/**
 * Class: FormValidator
 * Validates an input form by iterating through a list of provided fields and checking for expected values
 * 
 * Parameters:
 * formID - The ID of the form to validate.
 * fields - an array of JSON objects specifying which fields to validate
 * options - a JSON object with optional parameters for this class - See options below
 * 
 * Field format is as follows:
 * {
 * 	el:$('BirthYearSelect'),
 * 	format:'select',
 * 	options:{...}
 * }
 * 
 * el Accepts any proper reference to DOM element ($,$E)
 * format accepts: select,radio,confirm,checkbox,text,alphanum,email,number,phone,url
 * options accepts:
 * 		min: minimum characters to input (number) - Available for any text input format
 * 		max: maximum characters to input - Available for any text input format
 * 		confirmWith: ID of element to confirm against (compare that both fields have the same value) - Available ONLY for "confirm" format
 * 		empty: STRING - the empty (unselected) value of a selection combo box - Available ONLY form "select" format
 * 
 * Examples:
 * 		el:$('PasswordRegistration'),format:'alphanum',options:{min:3,max:12}}
 * 		{el:$('ConfPasswordRegistration'),format:'confirm',options:{'confirmWith':'PasswordRegistration'}}
 * 		{el:$('BirthDaySelect'),format:'select',options:{'empty':'**'}}
 */
var Validator = new Class({
	/**
	 * Options:
	 * invalidClassName: the CSS class name appended to invalid form elements
	 * parentClassName: the CSS class name of the element containing the validated form element (this element receives the Invalid class)
	 * onSuccess: event that is fired on successful validation
	 * onfailure: event that is fired on failed validation
	 * validateOnKeyDown: attach validation to keydown event
	 * validateOnFocus: attach validation to field focus event
	 * validateOnBlur: attach validation to field blur event
	 * preventSubmit: (true/false) - prevent form submit (event.stop())
	 * 
	 */
	options:{
		invalidClassName:'Invalid',
		parentClassName:'FormRow',
		onSuccess:Class.empty,
		onfailure:Class.empty,
		validateOnKeyDown:false,
		validateOnFocus:false,
		validateOnBlur:false,
		preventSubmit: true,
		scrollToError:true
	},
	/**
	 * Method: initialize
	 * Initializes the class
	 * Parameters:
	 * @param {Object} formID
	 * @param {Object} fields
	 * @param {Object} options
	 */
	initialize:function(formID,fields,options){
		this.setOptions(options);
		this.form = $(formID);
		//this.form.reset(); // commented by Alon & Zohar to allow the wiki form duplication to work
		this.formID = formID;
		this.fields = this.mergeFields(fields);
		this.allErrors = '';
		
		this.errorField = $E('.FormErrors',formID) || false;
		
		this.fields.each(function(el){
			this.initFieldEvents(el);
		}.bind(this));
		
		this.onSuccess = this.options.onSuccess;
		this.onFailure = this.options.onFailure;
	},
	initFieldEvents: function(el){
		var field = el.el;
		var p = el.el;
		while(!p.hasClass(this.options.parentClassName)){
			p = p.getParent();
		}
		
		el.parent = p;
		switch(el.format){
			case 'select':
			case 'selectDate':
				if (this.options.validateOnFocus) {
					field.addEvent('change', this.validateField.bindWithEvent(this, el));
				}
				break;
			case 'radio':
				if (this.options.validateOnFocus) {
					if (!window.ie) {
						field.addEvent('click', this.validateField.bindWithEvent(this, el));
					} else {
						field.addEvent('click', this.ieValidateWrap.bindWithEvent(this, el));
					}
				}
				break;
			case 'group':
				if (this.options.validateOnFocus) {
					if(!window.ie) field.addEvent('click',this.validateField.bindWithEvent(this,el));
					else field.addEvent('click',this.ieValidateWrap.bindWithEvent(this,el));
				}
				break;
			default:
				if (this.options.validateOnKeyDown) {
					field.addEvent('keyup', this.validateField.bindWithEvent(this, el));
				}					
				if (this.options.validateOnFocus) {
					field.addEvent('focus', this.validateField.bindWithEvent(this, el));
				}
				if (this.options.validateOnBlur) {
					field.addEvent('blur', this.validateField.bindWithEvent(this, el));
				}
				break;
		}
	},
	/**
	 * Method: mergeFields
	 * Takes a Hash of fields/field groups and merges it into an array of unique fields
	 * 
	 * Parameters:
	 * h (@array) - Array of fields to merge
	 */
	mergeFields:function(h){
		var tmp = [];
		var i = 0;
		h.each(function(el){
			var e = el.el;
			if (!el.options) {
				el.options = {};
			}
			if(!e.each){
				tmp[i] = {
					el:el.el,
					format:el.format,
					options:el.options
				};
				i++;
			} else {
				var c = 'Group'+i;
				e.each(function(els){
					e.addClass(c);
					tmp[i] = {
						el:els,
						format:el.format,
						options:el.options
					};
					i++;
				});
			}
		});
		return tmp;
	},
	addField: function(field){
		this.initFieldEvents(field);
		this.fields.push(field);
	},
	removeField: function(field){
		this.fields.remove(field);
	},
	/**
	 * Method: validateField
	 * Validates a field against a known format
	 * 
	 * Parameters:
	 * e - Event
	 * v - validation hash containing :
	 * 		el: reference to validated element
	 * 		parent: reference to the parent element that receives the Invalid classname
	 * 		format: format to validate against
	 * 		options: options hash with the same options passed to the class' fields array
	 */
	validateField:function(e,v){
		try {
			var el = v.el;
			var value = el.value.trim();
			var valid = false;
			var tmp = '';
			var req = v.options.required === undefined || (v.options.requires && v.options.requires.value != '') ? true : v.options.required;
			switch(v.format){
				case 'select':
					var empty = v.options.empty ? v.options.empty : '';
					if(value != empty) valid = true;
					break;
				case 'radio':
					$ES('input[name="'+el.getProperty('name')+'"]',this.form).each(function(el){
						if(el.checked) valid = true;
					}.bind(this));
					break;
				case 'group':
					var selectCount = 0;
					$ES('.'+el.className.replace(/(Over|Down)/gi,''),this.form).each(function(el){
						if (el.checked && !el.disabled) {
							valid = true;
							selectCount+=1;
						}
					}.bind(this));
					if(!req && selectCount == 0){
						valid = !req;
					} else {
						valid = v.options.maxSelect >= selectCount;
					}
					break;
				case 'selectDate':
					var els = $ES('.'+el.className);
					var date = new Date(els[v.options.yPos].value,els[v.options.mPos].value-1,els[v.options.dPos].value,0,0,0,0);
					var diff = date - v.options.minDate;
					valid = isNaN(diff) || diff > 0 ? false : true;
					break;
				case 'confirm':
					if(value == $(v.options.confirmWith).value) valid = true;
					break;
				case 'checkbox':
					if(el.checked) valid = true;
					break;
				case 'comparison':
					if(value.length < v.options.min) break;
					value = value.trim();
					v.options.compareWith = v.options.compareWith.trim();
					
					var val1 = value.substr(0,1).toUpperCase()+value.substr(1,value.length);
					var val2 = v.options.compareWith.substr(0,1).toUpperCase()+v.options.compareWith.substr(1,v.options.compareWith.length);
					valid = val1 == val2;
					if(v.options.invert) valid = !valid;
					break;
				default:
					var exp = '';
					switch(v.format){
						case 'text':
							exp = /^[a-z0-9\W\._%-\{\}\[\]\|\#]+$/i;
							break;
						case 'cleantext':
							//exp = /^[^<>\{\}\[\]\|]+[a-z0-9\W]+$/i;
							//exp = /^(\s*[^<>\{\}\[\]\|\,\s]+)(\s+[^<>\{\}\[\]\|\,\s]+\s*)*$/i;
							exp = /^(\s*[^<>\{\}\[\]\|\s]+)(\s+[^<>\{\}\[\]\|\s]+\s*)*$/i;
							break;
						case 'tags':
							exp = /^(\s*[^<>\{\}\[\]\|\,\s]{1,64})(\s+[^<>\{\}\[\]\|\,\s]{1,64}\s*)*$/i;
							break;
						case 'tags_comma':
							exp = /^([^<>\{\}\[\]\|\,]{1,64},?)(,\s?[^<>\{\}\[\]\|\,]{1,64},?)*$/i;
							break;
						case 'alphanum':
							exp = /^[a-zA-Z0-9]+$/i;
							break;
						case 'email':
							exp = /^[a-z0-9\.\_%-]+@[a-z0-9\.\-]+\.[a-z]{2,4}$/i;
							break;
						case 'nickname':
							exp = /^([^\[\]\{\}\<\>\|\:\?\/\\\#]*)$/i; 
							break;
						case 'number':
							exp = /^[\-\+]?\d*\.?\d+$/;
							break;
						case 'phone':
							exp = /^[\d\s \(\)\.\-\+]+$/;
							break;
						case 'url':
							exp = /^(([\w]+:)?\/\/)?(([\d\w]|%[a-fA-f\d]{2,2})+(:([\d\w]|%[a-fA-f\d]{2,2})+)?@)?([\d\w][\-\d\w]{0,253}\.)+[\w]{2,4}(:[\d]+)?(\/([\-+_~.\d\w]|%[a-fA-f\d]{2,2})*)*(\?(&?([\-+_~.\d\w]|%[a-fA-f\d]{2,2})=?)*)?(#([\-+_~.\d\w]|%[a-fA-f\d]{2,2})*)?$/;
							break;
						case 'expression':
							exp = v.options.exp;
							break;
					}
					var min = v.options.min ? v.options.min : 2;
					if((exp.test(value) && value.length >= min) || (!req && value.length == 0)) valid = true;
					if(v.options.max){
						if(value.length > v.options.max) el.value = el.value.substr(0,v.options.max);
					}
					break;
			}
			if(valid){
				v.parent.removeClass(this.options.invalidClassName);
			} else{
				v.parent.addClass(this.options.invalidClassName);
			}
		} catch(err){}
		return valid;
	},
	/**
	 * Method: ieValidateWrap
	 * wraps around validateField method but always returns true
	 * Fixes IE onclick bug in checkboxes/radio buttons
	 * 
	 * Returns:
	 * 		true - successful validation
	 * 
	 */
	ieValidateWrap:function(e,v){
		this.validateField(e,v);
		return true;
	},
	/**
	 * Method: validateForm
	 * Iterates through the fields array and validates each field using this.validateField method.
	 * 
	 * Returns:
	 * 		true - successful validation
	 * 		false - unsuccessful validation
	 * 
	 */
	validateForm:function(e){
		if(e){
			new Event(e).preventDefault();
		}
		var valid = true;
		this.allErrors = '';
		this.fields.each(function(el){
			if(!this.validateField(null,el)) valid = false;
		}.bind(this));
		if(!valid){
			this.fireEvent('onFailure');
			if(this.options.scrollToError){
				window.scrollTo(0,$E('.'+this.options.invalidClassName).getCoordinates().top-50);
			}
			if (this.errorField) {
				this.errorField.addClass(this.options.invalidClassName).setHTML('Invalid information entered. Please review and correct the marked errors below');
			}
			return false;
		} else {
			this.fireEvent('onSubmit');
			if (this.errorField) {
				this.errorField.removeClass(this.options.invalidClassName).setHTML('');
			}
			if(!this.options.ajaxMode){
				return true;
			} else{
				return false;
			}
		}
	},
	/**
	 * Method: reset
	 * Resets form and validation errors
	 */
	reset:function(){
		this.form.reset();
		this.resetErrors();
	},
	resetErrors:function(){
		$ES('.'+this.options.invalidClassName).removeClass(this.options.invalidClassName);
		if(this.errorField) this.errorField.removeClass(this.options.invalidClassName);
		this.allErrors = '';		
	},
	/**
	 * Method: getFieldById
	 * 	Returns the relevant field inside the class' fields array according to a given element ID
	 * 
	 * Arguments:
	 * 	id - ID of the element to find in the fields array
	 * 
	 * Returns:
	 * 	field - the relevant element in the field array
	 *
	 */
	getFieldById:function(id){
		j = null;
		this.fields.each(function(el,i){
			if(el.el.getProperty('id') == id){
				j = i;
			}
		});
		if(j != null){
			return this.fields[j];
		} else {
			return false;
		}
	}
});

Validator.implement(new Options);
Validator.implement(new Events);
