/**
 * @author ricky
 * 
 * Объект проверки правильности заполнения полей формы
 * 
 */
var FormValidator = {
	/**
	 * Показывать или не показывать ошибки
	 * 
	 * @access private
	 */
	_debug : false,
	/**
	 * Список ошибок, возникающих при проверке входных данных
	 * 
	 * @access private
	 */
	_error_list : {
		no_data : new Error('Object invocation with no parameters!'),
		no_form : new Error('No form with such id found!'),
		no_fields : new Error('No entry data found!'),
		wrong_entry_data_type : new Error('Entry data should be an array!'),
		no_elements : new Error('Entry array is empty!'),
		objects_needed : new Error('Entry array should consist of objects!'),
		no_element_id : new Error('Not all of the objects have a link to the element to check!'),
		no_element_type : new Error('Not all of the objects have types!'),
		unknown_element_type : new Error('Unidentified field type!')
	},
	/**
	 * Массив с накопившимися ошибками, которые нужно показать
	 * 
	 * @access private
	 */
	_errors : [],
	/**
	 * Объект, содержащий: ключи - идентификаторы таблицы, значения - массивы с
	 * объектами для провекри
	 * 
	 * @access private
	 */
	_fields : {},
	/**
	 * регулярные выражения для определенных условий
	 * 
	 * @access private
	 */
	_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
	},
	/**
	 * Типы полей для проверки
	 * 
	 * @access private
	 */
	_types : ['text', 'radio', 'select', 'checkbox'],
	/**
	 * Булево значение. если есть ошибки при проверке формы, то тру, иначе фолс
	 * 
	 * @access private
	 */
	_form_valid : true,
	/**
	 * Добавляем ошибку в массив для дальнейшего показа
	 * 
	 * @access private
	 * @param {String}
	 *            type тип возникшей ошибки
	 * @return {void}
	 */
	_add_error : function(type) {
		if (typeof this._error_list[type] != 'undefined') {
			this._errors.include(type);
		}
	},
	/**
	 * если предыдущая проверка не прошла, то появляются элементы с описанием
	 * ошибок, либо сами элементы управления формой окрасились в другой цвет.
	 * При последующих проверках перед самой проверкой нужно вернуть всю форму в
	 * первоначальный вид. Для этого и существует метод _reset()
	 * 
	 * @access private
	 * @return {void}
	 */
	_reset : function(frm_id) {
		// Проверяем, есть ли что очищать
		if (!this._has_fields(frm_id)) {
			this._add_error('no_fields');
			return false;
		}

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

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

		// Сбрасываем ошибки
		this._errors = [];
	},
	/**
	 * Функция, проверяющее наличие элементов для проверки
	 * 
	 * @access private
	 * @return {Boolean}
	 */
	_has_fields : function(frm_id) {
		// Если есть поля для проверки, возвращаем правду =)
		if (typeof this._fields[frm_id] != 'undefined'
				&& this._fields[frm_id]['elements'].length) {
			return true;
		}
		return false;
	},
	/**
	 * Отображаем ошибки
	 * 
	 * @access private
	 */
	_throw_errors : function() {
		$each(this._errors, function(error) {
					throw this._error_list[error];
				}, this);
	},
	/**
	 * @param {Object}
	 *            obj - объект элемента, для которого нужно отобразить ошибку
	 * @access private
	 */
	_display_error : function(obj) {
		if (obj['alert']) {
			obj.alert.removeClass('hide');
		}
		if (typeof obj['element_class'] != 'undefined') {
			obj.element.addClass(obj.element_class);
		}
		this._form_valid = false;
	},
	/**
	 * Метод для проверки входных данных
	 * 
	 * @access private
	 * @param {String}
	 *            frm айдишник формы
	 * @param {Array}
	 *            elements массив объектов для проверки
	 * @return {Boolean}
	 */
	_prepare_fields : function(frm, elements) {
		var elements = $A(elements);

		var wrong_entry_type = 0;
		var has_no_element = 0;
		var has_no_type = 0;
		var has_wrong_type = 0;

		// Проверка на тип входных данных. должен быть массив
		if ($type(elements) != 'array') {
			this._add_error('wrong_entry_data_type');
			return false;
		}

		// Проверка на длину массива
		if (!elements.length) {
			this._add_error('no_elements')
			return false;
		}

		// проверка в одном цикле
		for (var i = 0, len = elements.length; i < len; i++) {
			if ($type(elements[i]) != 'object') {
				++wrong_entry_type;
				break;
			}

			// Проверка на наличие ссылки на элемент (element) в массиве
			// объектов
			if (typeof elements[i]['element'] == 'undefined') {
				++has_no_element;
				break;
			}
			// Проверка на наличие типа элемента (type) в массиве объектов
			if (typeof elements[i]['type'] == 'undefined') {
				++has_no_type;
				break;
			} else {
				// Проверка на соответствие типов
				if (!(this._types.indexOf(elements[i]['type']) + 1)) {
					++has_wrong_type;
					break;
				}
			}

			// если это одинарный элемент, то находим его идентификатор
			if (elements[i]['type'] == 'text'
					|| elements[i]['type'] == 'select') {
				elements[i]['element'] = frm.getElement('#'
						+ elements[i]['element']) || false;
			}
			// если это множественный элемент
			if (elements[i]['type'] == 'radio'
					|| elements[i]['type'] == 'checkbox') {
				var elms = frm.getElements('.' + elements[i]['element']
						+ ' > input')
				elements[i]['element'] = (elms.length) ? elms : false;
			}
			// если есть алерт
			if (typeof elements[i]['alert'] != 'undefined') {
				elements[i]['alert'] = frm.getElement('#'
						+ elements[i]['alert']) || false;
			}

			// если есть условие и оно текстовое, то преобразуем его в массив с
			// одни элементом.
			if (typeof elements[i]['condition'] != 'undefined'
					&& typeof elements[i]['condition'] == 'string') {
				elements[i]['condition'] = [elements[i]['condition']];
			}
			if (elements[i]['element'] && elements[i]['type'] == 'text'
					&& typeof elements[i]['condition'] != 'undefined'
					&& elements[i]['condition'].contains('integer')) {
				elements[i]['element'].addEvent('keydown', justNumber);
			}

		}

		if (wrong_entry_type) {
			this._add_error('objects_needed');
			return false;
		}
		if (has_no_element) {
			this._add_error('no_element_id');
			return false;
		}
		if (has_no_type) {
			this._add_error('no_element_type');
			return false;
		}
		if (has_wrong_type) {
			this._add_error('unknown_field_type');
			return false;
		}

		return elements;
	},
	/**
	 * Находит форму, или возвращает ложь
	 * 
	 * @param {String}
	 *            frm ссылка на идентификатор формы
	 * @access private
	 */
	_prepare_form : function(frm) {
		return $(frm) || false;
	},
	/**
	 * Включаем отображение ошибок
	 * 
	 * @access public
	 */
	enable_debug : function() {
		this._debug = true;
	},
	/**
	 * Выключаем отображение ошибок
	 * 
	 * @access public
	 */
	disable_debug : function() {
		this._debug = false;
	},
	/**
	 * удаляет все поля для проверки
	 * 
	 * @access public
	 * @return {Object} self
	 */
	empty_fiels : function() {
		this._fields = [];
		return this;
	},
	/**
	 * сеттер для полей и их атрибутов
	 * 
	 * @param {String}
	 *            frm_id идентификатор формы, в которой находятся объекты
	 * @param {Array}
	 *            elements массив с объектами для каждого из проверяемых полей
	 * @param {Bool}
	 *            update если true - добавляем элементы, если false -
	 *            переписываем новыми
	 * @access public
	 * @return {Object} self
	 */
	set : function(frm_id, elements, update) {
		// проверяем, послано ли достаточное количество данных
		if (typeof frm_id == 'undefined' || typeof elements == 'undefined') {
			this._add_error('no_data');
			if (this._debug) {
				this._throw_errors();
			}
			return false;
		}

		// смотрим, есть ли такая форма
		var frm = this._prepare_form(frm_id);
		if (!frm) {
			this._add_error('no_form');
			if (this._debug) {
				this._throw_errors();
			}
			return false;
		}

		// проверяем, соответствуют ли входные данные правде
		var elements = this._prepare_fields(frm, elements);
		if (!elements) {
			if (this._debug) {
				this._throw_errors();
			}
			return false;
		}

		// Смотрим, что делать с входными данными
		if (typeof update != 'indefined' && update) {
			// добавляем элементы к уже существующим
			var add_obj = {}
			add_obj[frm_id] = {};
			add_obj[frm_id]['form_link'] = frm;
			add_obj[frm_id]['elements'] = elements;
			$extend(this._fields, add_obj);
		} else {
			// обновляем все элементы
			this._fields = {};
			this._fields[frm_id]['form_link'] = frm;
			this._fields[frm_id]['elements'] = elements;
		}
		return this;
	},
	/**
	 * собственно метод, делающий валидацию
	 * 
	 * @param {String}
	 *            frm_id строчка с идентификатором таблицы, поля которой нужно
	 *            проверить
	 * @access public
	 * @return {void}
	 */
	validate : function(frm_id) {
		if (typeof frm_id == 'undefined' || typeof frm_id != 'string') {
			this._add_error('no_form');
			if (this._debug) {
				this._throw_errors();
			}
			return true;
		}

		// проверяем, есть ли поля для проверки
		if (!this._has_fields(frm_id)) {
			this._add_error('no_fields');
			if (this._debug) {
				this._throw_errors();
			}
			return true;
		}

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

		// Проверяем
		$each(this._fields[frm_id]['elements'], function(obj) {
			// смотрим какой у нас элемент
			if (obj.type == 'text' && obj.element) {
				// если текстовый, то возможно применение условий проверки
				// есть ли условия
				if (typeof obj['condition'] != 'undefined') {
					// если есть условия
					// смотрим, сколько условий имеется
					if ($type(obj['condition']) == 'array') {
						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);
									break;
								}
							}
							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);
										break;
									}
								}
								if (comparison == '<') {
									if (obj.element.value.trim().length > number) {
										this._display_error(obj);
										break;
									}
								}
							}
						}
					}
				} else {
					// если условий нет, то проверяем просто длину строки
					if (!obj.element.value.trim().length) {
						this._display_error(obj);
					}
				}
			}
			if ((obj.type == 'checkbox' || obj.type == 'radio') && obj.element) {
				var valid = false;
				for (var i = 0, len = obj.element.length; i < len; i++) {
					if (obj.element[i].checked) {
						valid = true;
						break;
					}
				}
				if (!valid) {
					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;
	}
}
