/**
 * @project MavSelectBox (Customizable Select boxes)
 * @author Dustin Hansen
 * @version 0.5.1
 * @url http://fuzecore.com, http://maveno.us
 * @license MIT Style License
 * 
 * @contributions
 * : Container additions for using selectbox in animations and bug fixes
 * : fixed multiselect bug, yayyy! ^_^
 * : Quentin Ambard - http://www.quentin.avricot.com
 */

var MavSelectBox = new Class({
	Implements: [Options, Events],

	options: {
		allowSplit: true,							// depricate for .filter()
		altClass: 'select-box-alt',
		alternate: false,
		alternateOdd: false,
		attachResize: true,
		container: null,
		disableClass: 'disabled',
		elem: null,
		filter: null,
		fxOptions: {},
		groupClass: 'select-box-options-group',
		maxShow: null,
		minShow: 3,
		optionClass: 'select-box-opt',
		selectboxClass: 'select-box',
		selectClass: 'selected',
		selectmenuClass: 'select-box-options',
		separator: '--',							// depricate for .filter()
		showStyles: false,
		size: 1,
		template: '<span>%s</span>',
		tmplt_regex: null,
		useFx: true,

		onHide: $empty(),
		onOver: $empty(),
		onSelect: $empty(),
		onShow: $empty()
	},

	container: null,
	element: null,
	focused: null,
	length: 0,
	selected: null,
	selectedIndex: 0,
	showing: false,
	textSearch: '',
	version: '0.5.1',

	initialize: function(_options) {
		var opts = ($type(_options) != 'object' ? {'elem':_options} : _options);
		if (!$defined(opts.elem)) return;

		this.setOptions(opts);

		if (this.options.filter) {
			this.filter = this.options.filter;
		}

		this.element = $(this.options.elem);
		this.elementCopy = this.element.clone().set({
			'id':this.element.get('id'),
			'name':this.element.get('name')
		});

		if (!$defined(this.options.container) || (this.container = $(this.options.container)) == null) {
			this.container = null;
		}

		this.optClass = 'li[class*=' + this.options.optionClass + ']';

		this.create_select();
		if (this.options.attachResize) { window.addEvent('resize', function() { if (this.showing) { this.show(); } }.bind(this)); }
	},

	destroy: function(_revert) {
		if (_revert) {
			if (this.selected && $defined(this.selected.retrieve('value'))) {
				$each(this.elementCopy.options, function(_elem) {
					if (_elem.value == this.selected.retrieve('value')) { _elem.selected = true; }
				}, this);
			}

			this.elementCopy.replaces($(this.element.get('id')));
		}

		this.remove_events();
		this.elementSelect.destroy();
	},

	create_select: function() {
		var wh = this.element.getSize();
		this.eid = this.element.get('id');

		// create the select element
		this.elementSelect  = new Element('div', {
			'class': this.options.selectboxClass, 
			'styles': { 'width': wh.x, 'height': wh.y }
		}).inject(this.element, 'after');

		// create display element for selectbox
		this.elementDisplay = new Element('a', {'href':'javascript:void(0)'}).inject(this.elementSelect, 'top');
		if (this.elementCopy.get('tabindex') != 0) {
			this.elementDisplay.set('tabindex', this.elementCopy.get('tabindex'));
		}

		this.elementDisplay.setStyles({'height': (wh.y-5), 'line-height': (wh.y-5)});
		this.add_events();

		// create the options element
		this.elementOptions = new Element('ul', {
			'styles': { 'width': wh.x },
			'opacity': (this.options.useFx ? 0 : 1),
			'class': this.options.selectmenuClass
		}).inject(this.elementSelect);

		// create the fx object if useFx is set
		this.fx = this.options.useFx ? new Fx.Tween(this.elementOptions, $merge({
			'duration': '200', 
			'link': 'cancel'
		}, this.options.fxOptions)) : null;

		// loop thru existing options and recreate
		$each(this.element.getChildren(), this.create_option.bind(this));

		// set alternating
		if (this.options.alternate) {
			this.elementOptions.getElements(this.optClass + ':' + (this.options.alternateOdd ? 'odd': 'even')).addClass(this.options.altClass);
		}

		// set default selected option and dislpay value
		(this.selected = this.elementOptions.getElement('li.' + this.options.selectClass)).removeClass(this.options.selectClass);
		this.selectedIndex = this.selected.retrieve('idx');

		this.elementDisplay.set({
			'html': this.selected.get('html'),
			'class': (this.options.showStyles ? this.selected.get('class') : ''),
			'style': (this.options.showStyles ? this.selected.get('style') : '')
		});

		// store option menu coords info
		this.elementOptions.store('coords', this.elementOptions.getCoordinates()).setStyles({'visibility':'','display':'none'});

		// replace select element with hidden input by same id/name
		this.element = new Element('input', {
			'type': 'hidden',
			'value': this.element.get('value'),
			'id': this.element.get('id'),
			'name': this.element.get('name')
		}).replaces(this.element).set('id', this.eid);
	},

	create_option: function(_opt, _idx, _group) {
		// get option information
		var val = _opt.get('value'), selected = !!(_opt.selected);
		var text = (_opt.get('label') ? _opt.get('label') : (_opt.get('text') || '&nbsp;'));

		// determine class for option
		var opt_class = (_opt.get('tag')=='optgroup'?' optgroup unselectable':' ' + this.options.optionClass) + (selected&&!_opt.disabled?' '+this.options.selectClass:'') +
						(_opt.disabled?' '+this.options.disableClass:'');

		// create li replacement for select option
		var new_option = new Element('li', {
			'id': this.eid + '_opt' + _idx,
			'html': this.filter(text, this.options.tmplt_regex, this.options.template),
			'style': _opt.get('style'),
			'class': _opt.get('class') + opt_class
		}).store('value', _opt.get('value'))
		  .store('idx', (_opt.get('tag')!='optgroup'?(++this.length):''))
		  .addEvents({
			'mouseover': this.over.bind(this),
			'mousedown': this.select.bind(this)
		}).inject(($(_group) || this.elementOptions));

		new_option.store('coords', new_option.getCoordinates());
		
		if (_opt.get('tag') == 'optgroup') {
			var optgroup = new Element('ul', {'class': this.options.groupClass}).inject(new_option);
			$each(_opt.getChildren(), function(_sopt, _sidx) {
				this.create_option(_sopt, (_idx + '' + _sidx), optgroup);
			}, this);
		}

		// THIS TO BE REPLACED WITH this.filter()
		// if option.text matches this.options.separator split and go left / right with text
		if (this.options.allowSplit && text.match(new RegExp(this.options.separator))) {
			text = text.split(this.options.separator);
			this.elementOptions.lastChild.set('html', '<span><span class="goleft">' + text[0].trim() + '</span><span class="goright">' + text[1].trim() + '</span><br style="clear:both" /></span>');
		}
	},
	
	filter: function(_str, _regx, _tmplt) {
		return _tmplt.replace(/\%s/i, _str);
	},

	inject: function(_option, _where) {
		
	},
	
	dispose: function(_elem) {
		
	},
	
	add_events: function() {
		this.elementDisplay.addEvents({
			'click': this.show.bind(this),
			'keydown': this.key_option.bind(this),
			'blur': this.hide.bind(this)
		});
	},
	
	remove_events: function() {
		this.elementDisplay.removeEvents({
			'click': this.show.bind(this),
			'keydown': this.key_option.bind(this),

			'blur': this.hide.bind(this)
		});
	},

	key_option: function(e) {
		e = new Event(e);
		if (e.key != 'tab') {
			e.stop();
			switch(e.key) {
				case 'esc':
					this.hide();
					break;

				case 'enter':
					this.select(this.selected);
				case 'tab':
					this.hide();
					break;
				
				case 'up': case 'down':
					if (e.alt) { this.show(); }
					this.select(e.key);
					break;
				
				case 'shift': case 'control': case 'alt':
					break;

				default:
					this.search(e.key);
			}
		}
	},

	search: function(_key, _retrying) {
		this.textSearch += _key;
		var option_elems = this.get_options(), str_found=false, elem = false;
		// var option_elems = this.get_options('li[text^=' + this.textSearch +']'), str_found=false, elem = false;

		for(var i=0; i<option_elems.length; i++) {
			var elem = option_elems[i]
			if ((elem.get('text')).match(new RegExp('^' + this.textSearch, 'i'))) {
				if (this.selected != elem) { this.select(elem); }
				str_found = true;
				break;
			}
		}

		if (str_found === false) {
			this.textSearch = '';
			if (!_retrying) { this.search(_key, true); }
		}
	},

	get_options: function(_selector) {
		return this.elementOptions.getElements((_selector || this.optClass));
	},

	// is there a better way to do this?
	determine: function(_elem) {
		var elem = ($type(_elem) == 'element' ? (_elem.get('tag') != 'li' ? _elem.getParent('li') : _elem) : this.get_options());

		if ($type(_elem) != 'element') {
			var fromIdx = ((this.focused && this.focused != this.selected) ? this.focused : this.selected).retrieve('idx');

			elem = elem.filter(function(_el) {
						return (!$(_el).hasClass(this.options.disableClass) && 
								((_elem == 'up' && $(_el).retrieve('idx') < fromIdx ) ||
								 (_elem == 'down' && $(_el).retrieve('idx') > fromIdx)));
			}, this);

			elem = elem[0] ? (_elem == 'up' ? elem.reverse() : elem)[0] : elem;
		}

		return elem;
	},

	over: function(e) {
		e = new Event(e);
		var elem = ($(e.target).get('tag') != 'li' ? $(e.target).getParent('li') : $(e.target));

		if (!elem.hasClass(this.options.disableClass) && !elem.hasClass('unselectable')) {
			if ($type(this.focused) == 'element') { this.focused.removeClass(this.options.selectClass); }
			(this.focused = elem).addClass(this.options.selectClass);
			this.fireEvent('over');
		}
	},

	select: function(_elem) {
		var elem = ($type(_elem) == 'event' ? new Event(_elem).target : _elem);
		elem = this.determine(elem);
		
		if (elem && !elem.hasClass(this.options.disableClass) && !elem.hasClass('unselectable')) {
			if (this.focused) { this.focused.removeClass(this.options.selectClass); }
			if (this.showing === true) {
				(this.focused = this.selected = elem).addClass(this.options.selectClass);
				this.scroll();
			} else {
				this.selected = elem;
			}

			this.element.set('value', this.selected.retrieve('value'));
			this.selectedIndex = this.selected.retrieve('idx');

			this.elementDisplay.set({
				'html': this.selected.get('html'),
				'class': (this.options.showStyles ? this.selected.get('class') : ''),
				'style': (this.options.showStyles ? this.selected.get('style') : '')
			}).removeClass(this.options.selectClass).removeClass(this.options.altClass);

			this.fireEvent('select', this.selected);
		}
	},

	scroll: function() {
		var sElem = this.elementOptions.getCoordinates();
		var selElem = this.selected.getCoordinates();
		var elScrollTop = this.elementOptions.scrollTop;

		if ((elScrollTop + sElem.height) < (selElem.top - sElem.top + 5)) {
			this.elementOptions.scrollTop = (selElem.top - sElem.top - sElem.height + selElem.height);
		}
		else if ((selElem.top - sElem.top + selElem.height) < (elScrollTop + 5)) {
			this.elementOptions.scrollTop = (selElem.top - sElem.top);
		}
	},

	show: function() {
		var coords = this.elementOptions.retrieve('coords');
		var sElem = this.elementSelect.getCoordinates(), sElem_top = (sElem.y + sElem.height);

		if (this.container) {
			sElem_top -= this.container.getStyle('top');
			sElem.left -= this.container.getStyle('left');
		}

		var h = ((window.getSize().y + window.getScroll().y) - sElem_top);
		var height = (coords.height >= h ? 0 : 'auto'), showing = 0;

		if (coords.height >= h) {
			$each(this.get_options(), function(_elem) {
				var eH = _elem.retrieve('coords').height;
				if (height < h && (height + eH) < h) { height += eH; showing++; }
			}, this);

			if (showing < this.options.minShow) {
				height = (sElem.top < coords.height ? sElem.top - 10 : coords.height);
				sElem_top = sElem.top - height - 1;
			}
		}

		this.elementOptions.setStyles({
			'display': '', 
			'height': height, 
			'top': sElem_top, 
			'left': sElem.x,
			'margin': 0
		});
		this.scroll();

		this.showing = true;
		this.focused = this.selected;
		this.focused.addClass(this.options.selectClass);
		this.fireEvent('show');

		if (this.options.useFx) { this.fx.start('opacity', 0, 1); }
		
		// fixes chrome/safari focus bug
		this.elementDisplay.focus();
	},

	hide: function(e) {
		if (this.showing) {
			if (this.options.useFx) {
				this.fx.start('opacity', 1, 0).chain(function() {
					this.elementOptions.scrollTop = 0;
					this.elementOptions.setStyle('display', 'none');

					if (this.focused) {
						this.focused.removeClass(this.options.selectClass);
					}
					this.showing = this.focused = false;
				}.bind(this));
			} else {
				this.elementOptions.setStyle('display', 'none');

				if (this.focused) {
					this.focused.removeClass(this.options.selectClass);
				}
				this.showing = this.focused = false;
			}

			this.fireEvent('hide');
		}

		this.textSearch = '';
	}
});



/* FORM.CHECK */

var FormCheck = new Class({
	
	Implements: [Options, Events],

	options : {
		
		tipsClass: 'fc-tbx',				//tips error class
		errorClass: 'fc-error',				//div error class
		fieldErrorClass: 'fc-field-error',	//error class for elements
		
		trimValue : false,					//trim (remove whitespaces before and after) the value
		validateDisabled : false,			//skip validation on disabled input if set to false.
		
		submitByAjax : false,				//false : standard submit way, true : submit by ajax
		ajaxResponseDiv : false,			//element to inject ajax response into (can also use onAjaxSuccess) [cronix] 
		ajaxEvalScripts : false,			//use evalScripts in the Request response [cronix] 
		onAjaxRequest : $empty,				//Function to fire when the Request event starts 
		onAjaxSuccess : $empty,				//Function to fire when the Request receives .  Args: response [the request response] - see Mootools docs for Request.onSuccess 
		onAjaxFailure : $empty,				//Function to fire if the Request fails 

		display : {
			showErrors : 0,
			titlesInsteadNames : 0,
			errorsLocation : 1,
			indicateErrors : 1,
			indicateErrorsInit : 0,
			keepFocusOnError : 0,
			checkValueIfEmpty : 1,
			addClassErrorToField : 0,
			fixPngForIe : 1,
			replaceTipsEffect : 1,
			flashTips : 0,
			closeTipsButton : 1,
			tipsPosition : "left",
			tipsOffsetX : -30,
			tipsOffsetY : -15,
			listErrorsAtTop : false,
			scrollToFirst : true,
			fadeDuration : 300
		},
		
		alerts : {
			required: "This field is required.",
			alpha: "This field accepts alphabetic characters only.",
			alphanum: "This field accepts alphanumeric characters only.",
			nodigit: "No digits are accepted.",
			digit: "Please enter a valid integer.",
			digitltd: "The value must be between %0 and %1",
			number: "Please enter a valid number.",
			email: "Please enter a valid email.",
			phone: "Please enter a valid phone.",
			url: "Please enter a valid url.",
			
			confirm: "This field is different from %0",
			differs: "This value must be different of %0",
			length_str: "The length is incorrect, it must be between %0 and %1",
			length_fix: "The length is incorrect, it must be exactly %0 characters",
			lengthmax: "The length is incorrect, it must be at max %0",
			lengthmin: "The length is incorrect, it must be at least %0",
			checkbox: "Please check the box",
			radios: "Please select a radio",
			select: "Please choose a value"
		},
		
		regexp : {
			required : /[^.*]/,
			alpha : /^[a-z ._-]+$/i,
			alphanum : /^[a-z0-9 ._-]+$/i,
			digit : /^[-+]?[0-9]+$/,
			nodigit : /^[^0-9]+$/,
			number : /^[-+]?\d*\.?\d+$/,
			email : /^[a-z0-9._%-]+@[a-z0-9.-]+\.[a-z]{2,4}$/i,
			phone : /^[\d\s ().-]+$/,
			url : /^(http|https|ftp)\:\/\/[a-z0-9\-\.]+\.[a-z]{2,3}(:[a-z0-9]*)?\/?([a-z0-9\-\._\?\,\'\/\\\+&amp;%\$#\=~])*$/i
		}
	},
	
	/*
	Constructor: initialize
		Constructor
	
		Add event on formular and perform some stuff, you now, like settings, ...
	*/
	initialize : function(form, options) {
		if (this.form = $(form)) {
			this.form.isValid = true;
			this.regex = ['length'];
			this.setOptions(options);
			
			//internalization
			if (typeof(formcheckLanguage) != 'undefined') this.options.alerts = $merge(this.options.alerts, formcheckLanguage);
			
			this.validations = [];
			this.alreadyIndicated = false;
			this.firstError = false;
			
			var regex = new Hash(this.options.regexp);
			regex.each(function(el, key) {
				this.regex.push(key);
			}, this);

			this.form.getElements("*[class*=validate]").each(function(el) {
				this.register(el);
			}, this);
			
			this.form.addEvents({
				"submit": this.onSubmit.bind(this)
			});
			
			if(this.options.display.fixPngForIe) this.fixIeStuffs();
			document.addEvent('mousewheel', function(){
				this.isScrolling = false;
			}.bind(this));
		}
	},
	
	/*
	Function: register
		Allows you to declare afterward new fields to the formcheck, to check dynamically loaded fields for example.
	
	Example:
		(code)
		<script type="text/javascript">
			window.addEvent('domready', function() {
				formcheck = new FormCheck('form_id');
			});
			
			// ...some code...
			
			var newField = new Element('input', {
				class	: "validate['required']",
				name	: "new-field"
			}).inject('form_id');
			formcheck.register(newField);
			
			new Element('input', {
				class	: "validate['required']",
				name	: "another-field",
				id		: "another-field"
			}).inject('form_id');
			formcheck.register($('another-field'));
		</script>
		(end code)
	
	See also:
		<FormCheck::dispose>
	*/
	register : function(el) {
		el.validation = [];
		el.getProperty("class").split(' ').each(function(classX) {
			if(classX.match(/^validate(\[.+\])$/)) {
				var validators = eval(classX.match(/^validate(\[.+\])$/)[1]);
				for(var i = 0; i < validators.length; i++) {
					el.validation.push(validators[i]);
					if (validators[i].match(/^confirm\[/)) {
						var field = eval(validators[i].match(/^.+(\[.+\])$/)[1].replace(/([A-Z0-9\._-]+)/i, "'$1'"));
						if (this.form[field].validation.contains('required')){
							el.validation.push('required');
						}
							
					}
				}
				this.addListener(el);
			}
		}, this);
	},
	
	/*
	Function: dispose
		Allows you to remove a declared field from formCheck
	
	Example:
		(code)
		<script type="text/javascript">
			window.addEvent('domready', function() {
				formcheck = new FormCheck('form_id');
			});
			
			// ...some code...
			
			formcheck.dispose($('obsolete-field'));
		</script>
		(end code)
	
	See also:
		<FormCheck::register>
	*/
	dispose : function(element) {
		this.validations.erase(element);
	},
	
	/*
	Function: addListener
		Private method
		
		Add listener on fields
	*/
	addListener : function(el) {
		this.validations.push(el);
		el.errors = [];
		
		if (this.options.display.indicateErrorsInit) {
			this.validations.each(function(el) {
				if(!this.manageError(el,'submit')) this.form.isValid = false;
			}, this);
			return true;
		} 
	
		if (el.validation[0] == 'submit') {
			el.addEvent('click', function(e){
				this.onSubmit(e);
			}.bind(this));
			return true;
		}

		if (this.isChildType(el) == false) el.addEvent('blur', function() {
			(function(){
				if(!this.fxRunning && (el.element || this.options.display.showErrors == 1) && (this.options.display.checkValueIfEmpty || el.value))
				this.manageError(el, 'blur')
			}.bind(this)).delay(100);
		}.bind(this))
		//We manage errors on radio
		else if (this.isChildType(el) == true) {
			//We get all radio from the same group and add a blur option
			var nlButtonGroup = this.form.getElements('input[name="'+ el.getProperty("name") +'"]');
			nlButtonGroup.each(function(radio){
				radio.addEvent('blur', function(){
					(function(){
						if((el.element || this.options.display.showErrors == 1) && (this.options.display.checkValueIfEmpty || el.value)) this.manageError(el, 'click');
					}.bind(this)).delay(100);
				}.bind(this))
			},this);
		}
	},
	
	/*
	Function: validate
		Private method
		
		Dispatch check to other methods
	*/
	validate : function(el) {
		el.errors = [];
		el.isOk = true;
		
		//skip validation and trim if specified
		if (!this.options.validateDisabled && el.get('disabled')) return true;
		if (this.options.trimValue && el.value) el.value = el.value.trim();
		
		el.validation.each(function(rule) {
			if(this.isChildType(el)) {
				if (this.validateGroup(el) == false) {
					el.isOk = false;
				}
			} else {
				var ruleArgs = [];
				
				if(rule.match(/^.+\[/)) {
					var ruleMethod = rule.split('[')[0];
					ruleArgs = eval(rule.match(/^.+(\[.+\])$/)[1].replace(/([A-Z0-9\._-]+)/i, "'$1'"));
				} else var ruleMethod = rule;
				
				if (this.regex.contains(ruleMethod) && el.get('tag') != "select") {
					if (this.validateRegex(el, ruleMethod, ruleArgs) == false) {
						el.isOk = false;
					}
				}
				
				if (ruleMethod == 'confirm') {
					if (this.validateConfirm(el, ruleArgs) == false) {
						el.isOk = false;
					}
				}
				if (ruleMethod == 'differs') {
					if (this.validateDiffers(el, ruleArgs) == false) {
						el.isOk = false;
					}
				}
				if (el.get('tag') == "select" || (el.type == "checkbox" && ruleMethod == 'required')) {
					if (this.simpleValidate(el) == false) {
						el.isOk = false;
					}
				}
				if(rule.match(/%[A-Z0-9\._-]+$/i) || (el.isOk && rule.match(/~[A-Z0-9\._-]+$/i))) {
					if(eval(rule.slice(1)+'(el)') == false) {
						el.isOk = false;
					}
				}
			}
		}, this);
		
		if (el.isOk) return true;
		else return false;
	},
	
	/*
	Function: simpleValidate
		Private method
		
		Perform simple check for select fields and checkboxes
	*/
	simpleValidate : function(el) {
		if (el.get('tag') == 'select' && el.selectedIndex <= 0) {
			el.errors.push(this.options.alerts.select);
			return false;
		} else if (el.type == "checkbox" && el.checked == false) {
			el.errors.push(this.options.alerts.checkbox);
			return false;
		}
		return true;
	},
	
	/*
	Function: validateRegex
		Private method
		
		Perform regex validations
	*/
	validateRegex : function(el, ruleMethod, ruleArgs) {
		var msg = "";
		if (ruleArgs[1] && ruleMethod == 'length') {
			if (ruleArgs[1] == -1) {
				this.options.regexp.length = new RegExp("^[\\s\\S]{"+ ruleArgs[0] +",}$");
				msg = this.options.alerts.lengthmin.replace("%0",ruleArgs[0]);
			} else if(ruleArgs[0] == ruleArgs[1]) {
				this.options.regexp.length = new RegExp("^[\\s\\S]{"+ ruleArgs[0] +"}$");
				msg = this.options.alerts.length_fix.replace("%0",ruleArgs[0]);
			} else {
				this.options.regexp.length = new RegExp("^[\\s\\S]{"+ ruleArgs[0] +","+ ruleArgs[1] +"}$");
				msg = this.options.alerts.length_str.replace("%0",ruleArgs[0]).replace("%1",ruleArgs[1]);
			}
		} else if (ruleArgs[0] && ruleMethod == 'length') {
			this.options.regexp.length = new RegExp("^.{0,"+ ruleArgs[0] +"}$");
			msg = this.options.alerts.lengthmax.replace("%0",ruleArgs[0]);
		} else {
			msg = this.options.alerts[ruleMethod];
		}
		if (ruleArgs[1] && ruleMethod == 'digit') {
			var regres = true;
			if (!this.options.regexp.digit.test(el.value)) {
				el.errors.push(this.options.alerts[ruleMethod]);
				regres = false;
			}
			if (ruleArgs[1] == -1) {
				if (el.value >= ruleArgs[0]) var valueres = true; else var valueres = false;
				msg = this.options.alerts.digitmin.replace("%0",ruleArgs[0]);
			} else {
				if (el.value >= ruleArgs[0] && el.value <= ruleArgs[1]) var valueres = true; else var valueres = false;
				msg = this.options.alerts.digitltd.replace("%0",ruleArgs[0]).replace("%1",ruleArgs[1]);
			}
			if (regres == false || valueres == false) {
				el.errors.push(msg);
				return false;
			}
		} else if (this.options.regexp[ruleMethod].test(el.value) == false)  {
			el.errors.push(msg);
			return false;
		}
		return true;
	},

	/*
	Function: validateConfirm
		Private method
		
		Perform confirm validations
	*/
	validateConfirm: function(el,ruleArgs) {
		
		var confirm = ruleArgs[0];
		if(el.value != this.form[confirm].value){
			if (this.options.display.titlesInsteadNames)
				var msg = this.options.alerts.confirm.replace("%0",this.form[confirm].getProperty('title'));
			else
				var msg = this.options.alerts.confirm.replace("%0",confirm);
			el.errors.push(msg);
			return false;
		}
		return true;
	},
	
	/*
	Function: validateDiffers
		Private method
		
		Perform differs validations
	*/
	validateDiffers: function(el,ruleArgs) {
		var differs = ruleArgs[0];
		if(el.value == this.form[differs].value){
			if (this.options.display.titlesInsteadNames)
				var msg = this.options.alerts.differs.replace("%0",this.form[differs].getProperty('title'));
			else
				var msg = this.options.alerts.differs.replace("%0",differs);
			el.errors.push(msg);
			return false;
		}
		return true;
	},
	
	/*
	Function: isChildType
		Private method
		
		Determine if the field is a group of radio or not.
	*/
	isChildType: function(el) {
		return ($defined(el.type) && el.type == 'radio') ? true : false;
	},
	
	/*
	Function: validateGroup
		Private method
		
		Perform radios validations
	*/
	validateGroup : function(el) {
		el.errors = [];
		var nlButtonGroup = this.form[el.getProperty("name")];
		el.group = nlButtonGroup;
		var cbCheckeds = false;
		
		for(var i = 0; i < nlButtonGroup.length; i++) {
			if(nlButtonGroup[i].checked) {
				cbCheckeds = true;
			}
		}
		if(cbCheckeds == false) {
			el.errors.push(this.options.alerts.radios);
			return false;
		} else {
			return true;	
		}
	},
	
	/*
	Function: listErrorsAtTop
		Private method
		
		Display errors
	*/
	listErrorsAtTop : function(obj) {
		if(!this.form.element) {
			 this.form.element = new Element('div', {'id' : 'errorlist', 'class' : this.options.errorClass}).injectTop(this.form);
		}
		if ($type(obj) == 'collection') {
			new Element('p').set('html',"<span>" + obj[0].name + " : </span>" + obj[0].errors[0]).injectInside(this.form.element);
		} else {
			if ((obj.validation.contains('required') && obj.errors.length > 0) || (obj.errors.length > 0 && obj.value && obj.validation.contains('required') == false)) {
				obj.errors.each(function(error) {
					new Element('p').set('html',"<span>" + obj.name + " : </span>" + error).injectInside(this.form.element);
				}, this);
			}
		}
	},
	
	/*
	Function: manageError
		Private method
		
		Manage display of errors boxes
	*/
	manageError : function(el, method) {
		var isValid = this.validate(el);
		if ((!isValid && el.validation.flatten()[0].contains('confirm[')) || (!isValid && el.validation.contains('required')) || (!el.validation.contains('required') && el.value && !isValid)) {
			if(this.options.display.listErrorsAtTop == true && method == 'submit')
				this.listErrorsAtTop(el, method);
			if (this.options.display.indicateErrors == 2 ||this.alreadyIndicated == false || el.name == this.alreadyIndicated.name)
			{
				if(!this.firstError) this.firstError = el;
				
				this.alreadyIndicated = el;
				
				if (this.options.display.keepFocusOnError && el.name == this.firstError.name) (function(){el.focus()}).delay(20);
				this.addError(el);
				return false;
			}
		} else if ((isValid || (!el.validation.contains('required') && !el.value)) && el.element) {
			this.removeError(el);
			return true;
		}
		return true;
	},
	
	/*
	Function: addError
		Private method
		
		Add error message
	*/
	addError : function(obj) {
		if(!obj.element && this.options.display.indicateErrors != 0) {
			if (this.options.display.errorsLocation == 1) {
				var pos = (this.options.display.tipsPosition == 'left') ? obj.getCoordinates().left : obj.getCoordinates().right;
				var options = {
					'opacity' : 0,
					'position' : 'absolute',
					'float' : 'left',
					'left' : pos + this.options.display.tipsOffsetX
				}
				obj.element = new Element('div', {'class' : this.options.tipsClass, 'styles' : options}).injectInside(document.body);
				this.addPositionEvent(obj);
			} else if (this.options.display.errorsLocation == 2){
				obj.element = new Element('div', {'class' : this.options.errorClass, 'styles' : {'opacity' : 0}}).injectBefore(obj);
			} else if (this.options.display.errorsLocation == 3){
				obj.element = new Element('div', {'class' : this.options.errorClass, 'styles' : {'opacity' : 0}});
				if ($type(obj.group) == 'object' || $type(obj.group) == 'collection')
					obj.element.injectAfter(obj.group[obj.group.length-1]);
				else
					obj.element.injectAfter(obj);
			}
		}					
		if (obj.element && obj.element != true) {
			obj.element.empty();
			if (this.options.display.errorsLocation == 1) {
				var errors = [];
				obj.errors.each(function(error) {
					errors.push(new Element('p').set('html', error));
				});
				var tips = this.makeTips(errors).injectInside(obj.element);
				if(this.options.display.closeTipsButton) {
					tips.getElements('a.close').addEvent('mouseup', function(){
						this.removeError(obj);
					}.bind(this));
				}
				obj.element.setStyle('top', obj.getCoordinates().top - tips.getCoordinates().height + this.options.display.tipsOffsetY);
			} else {
				obj.errors.each(function(error) {
					new Element('p').set('html',error).injectInside(obj.element);
				});
			}
			
			if (!this.options.display.fadeDuration || Browser.Engine.trident && Browser.Engine.version == 5 && this.options.display.errorsLocation < 2) {
				obj.element.setStyle('opacity', 1);
			} else {
				obj.fx = new Fx.Tween(obj.element, {
					'duration' : this.options.display.fadeDuration,
					'ignore' : true,
					'onStart' : function(){
						this.fxRunning = true;
					}.bind(this),
					'onComplete' : function() {
						this.fxRunning = false;
						if (obj.element && obj.element.getStyle('opacity').toInt() == 0) {
							obj.element.destroy();
							obj.element = false;
						}
					}.bind(this)
				})
				if(obj.element.getStyle('opacity').toInt() != 1) obj.fx.start('opacity', 1);
			}
		}
		if (this.options.display.addClassErrorToField && this.isChildType(obj) == false){
			obj.addClass(this.options.fieldErrorClass);
			obj.element = obj.element || true;
		}
			
	},
	
	/*
	Function: addPositionEvent
		
		Update tips position after a browser resize
	*/
	addPositionEvent : function(obj) {
		if(this.options.display.replaceTipsEffect) {
			obj.event = function(){
				new Fx.Morph(obj.element, {
					'duration' : this.options.display.fadeDuration
				}).start({ 
					'left':[obj.element.getStyle('left'), obj.getCoordinates().right + this.options.display.tipsOffsetX],
					'top':[obj.element.getStyle('top'), obj.getCoordinates().top - obj.element.getCoordinates().height + this.options.display.tipsOffsetY]
				});
			}.bind(this);
			
		} else {
			obj.event = function(){
				obj.element.setStyles({ 
					'left':obj.getCoordinates().right + this.options.display.tipsOffsetX,
					'top':obj.getCoordinates().top - obj.element.getCoordinates().height + this.options.display.tipsOffsetY
				});
			}.bind(this)
		}
		window.addEvent('resize', obj.event);
	},
	
	/*
	Function: removeError
		Private method
		
		Remove the error display
	*/
	removeError : function(obj) {
		this.alreadyIndicated = false;
		obj.errors = [];
		obj.isOK = true;
		window.removeEvent('resize', obj.event);
		if (this.options.display.errorsLocation >= 2 && obj.element) {
			new Fx.Tween(obj.element, {
				'duration': this.options.display.fadeDuration
			}).start('height', 0);
		}
		if (!this.options.display.fadeDuration || Browser.Engine.trident && Browser.Engine.version == 5 && this.options.display.errorsLocation == 1 && obj.element) {
			this.fxRunning = true;
			obj.element.destroy();
			obj.element = false;
			(function(){this.fxRunning = false}.bind(this)).delay(200);
		} else if (obj.element && obj.element != true) {
			obj.fx.start('opacity', 0);
		}
		
		if (this.options.display.addClassErrorToField && !this.isChildType(obj))
			obj.removeClass(this.options.fieldErrorClass);
	},
	
	/*
	Function: focusOnError
		Private method
		
		Create set the focus to the first field with an error if needed
	*/
	focusOnError : function (obj) {
		if (this.options.display.scrollToFirst && !this.alreadyFocused && !this.isScrolling) {
			if (!this.options.display.indicateErrors || !this.options.display.errorsLocation) {
				var dest = obj.getCoordinates().top-30;
			} else if (this.alreadyIndicated.element) {
				switch (this.options.display.errorsLocation){
					case 1 : 
						var dest = obj.element.getCoordinates().top;
						break;
					case 2 :
						var dest = obj.element.getCoordinates().top-30;
						break;
					case 3 :
						var dest = obj.getCoordinates().top-30;
						break;
				}
				this.isScrolling = true;
			}
			if (window.getScroll.y != dest) {
				new Fx.Scroll(window, {
					onComplete : function() {
						this.isScrolling = false;
						obj.focus();
					}.bind(this)
				}).start(0,dest);
			} else {
				this.isScrolling = false;
				obj.focus();
			}
			this.alreadyFocused = true;
		}
	},
	
	/*
	Function: fixIeStuffs
		Private method
		
		Fix png for IE6
	*/
	fixIeStuffs : function () {
		if (Browser.Engine.trident4) {
			//We fix png stuffs
			var rpng = new RegExp('url\\(([\.a-zA-Z0-9_/:-]+\.png)\\)');
			var search = new RegExp('(.+)formcheck\.css');
			for (var i = 0; i < document.styleSheets.length; i++){
				if (document.styleSheets[i].href.match(/formcheck\.css$/)) {
					var root = document.styleSheets[i].href.replace(search, '$1');
					var count = document.styleSheets[i].rules.length;
					for (var j = 0; j < count; j++){
						var cssstyle = document.styleSheets[i].rules[j].style;
						var bgimage = root + cssstyle.backgroundImage.replace(rpng, '$1');
						if (bgimage && bgimage.match(/\.png/i)){
							var scale = (cssstyle.backgroundRepeat == 'no-repeat') ? 'crop' : 'scale';
							cssstyle.filter =  'progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, src=\'' + bgimage + '\', sizingMethod=\''+ scale +'\')';
							cssstyle.backgroundImage = "none";
						}
					}
				}
			}
		}
	},
	
	/*
	Function: makeTips
		Private method
		
		Create tips boxes
	*/
	makeTips : function(txt) {
		var table = new Element('table');
			table.cellPadding ='0';
			table.cellSpacing ='0';
			table.border ='0';
			
			var tbody = new Element('tbody').injectInside(table);
				var tr1 = new Element('tr').injectInside(tbody);
					new Element('td', {'class' : 'tl'}).injectInside(tr1);
					new Element('td', {'class' : 't'}).injectInside(tr1);
					new Element('td', {'class' : 'tr'}).injectInside(tr1);
				var tr2 = new Element('tr').injectInside(tbody);
					new Element('td', {'class' : 'l'}).injectInside(tr2);
					var cont = new Element('td', {'class' : 'c'}).injectInside(tr2);
						var errors = new Element('div', {'class' : 'err'}).injectInside(cont);
						txt.each(function(error) {
							error.injectInside(errors);
						});
						if (this.options.display.closeTipsButton) new Element('a',{'class' : 'close'}).injectInside(cont);
					new Element('td', {'class' : 'r'}).injectInside(tr2);
				var tr3 = new Element('tr').injectInside(tbody);
					new Element('td', {'class' : 'bl'}).injectInside(tr3);
					new Element('td', {'class' : 'b'}).injectInside(tr3);
					new Element('td', {'class' : 'br'}).injectInside(tr3);			
		return table;
	},
	
	/*
	Function: reinitialize
		Private method		
		
		Reinitialize form before submit check
	*/
	reinitialize: function() {
		this.validations.each(function(el) {
			if (el.element) {
				el.errors = [];
				el.isOK = true;
				if(this.options.display.flashTips == 1) {
					el.element.destroy();
					el.element = false;
				}
			}
		}, this);
		if (this.form.element) this.form.element.empty();
		this.alreadyFocused = false;
		this.firstError = false;
		this.elementToRemove = this.alreadyIndicated;
		this.alreadyIndicated = false;
		this.form.isValid = true;
	},
	
	/*
	Function: submitByAjax
		Private method		
		
		Send the form by ajax, and replace the form with response
	*/
	
	submitByAjax: function() {
		var url = this.form.getProperty('action');
		this.fireEvent('ajaxRequest');
		new Request({
			url: url,
			method: this.form.getProperty('method'),
			data : this.form.toQueryString(),
			evalScripts: this.options.ajaxEvalScripts,
			onFailure: function(instance){
				this.fireEvent('ajaxFailure', instance);
			}.bind(this),
			onSuccess: function(result){
				this.fireEvent('ajaxSuccess', result);
				if(this.options.ajaxResponseDiv) $(this.options.ajaxResponseDiv).set('html',result);
			}.bind(this)
		}).send();
	},
	
	/*
	Function: onSubmit
		Private method		
		
		Perform check on submit action
	*/
	onSubmit: function(event) {
		this.reinitialize();
	
		this.validations.each(function(el) {
			var validation = this.manageError(el,'submit');
			if(!validation) this.form.isValid = false;
		}, this);
	    
		if (this.form.isValid) {
			if (this.options.submitByAjax) {
				new Event(event).stop();
				this.submitByAjax();
			}
		} else {
			new Event(event).stop();
			if (this.elementToRemove && this.elementToRemove != this.firstError && this.options.display.indicateErrors == 1) {
				this.removeError(this.elementToRemove);
			}
			this.focusOnError(this.firstError)
		}
	}
});




window.addEvent('domready', function() { 
var helpform = new FormCheck('helpform', {
					flashTips : '1',
					display : {
						scrollToFirst : false
					},
					alerts : {
						required : 'Please fill in this field.'
					}
				})
});
