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

		submit : true,						//false : just validate the form and do nothing else. Use onValidateSuccess event to execute some code
		submitAction: false,				//Action page used to submit the form data to.
		submitMethod: false,				//Method used to submit the form, valid options : 'post' or 'get'

		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
		onAjaxComplete : $empty,			//Function to fire when the Request is complete, before and regardless of Success or Failure
		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

		onSubmit		  : $empty,			//Function to fire when user submit the form
		onValidateSuccess : $empty,			//Function to fire when validation pass
		onValidateFailure : $empty,			//Function to fire when validation fails

		display : {
			showErrors : 0,
			titlesInsteadNames : 0,
			errorsLocation : 1,
			indicateErrors : 1,
			indicateErrorsInit : 0,
			keepFocusOnError : 0,
			checkValueIfEmpty : 1,
			addClassErrorToField : 0,
			removeClassErrorOnTipClosure : 0,
			fixPngForIe : 1,
			replaceTipsEffect : 1,
			flashTips : 0,
			closeTipsButton : 1,
			tipsPosition : "right",
			tipsOffsetX : -45,
			tipsOffsetY : 0,
			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.",
			image : 'This field should only contain image types',
			phone : "Please enter a valid phone.",
			phone_inter : "Please enter a valid international phone number.",
			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",
			words_min : "This field must concain at least %0 words, currently: %1 words",
			words_range : "This field must contain %0-%1 words, currently: %2 words",
			words_max : "This field must contain at max %0 words, currently: %1 words",
			checkbox : "Please check the box",
			checkboxes_group : 'Please check at least %0 box(es)',
			radios : "Please select a radio",
			select : "Please choose a value",
			select_multiple : "Please choose at least one value"
		},

		regexp : {
			required : /[^.*]/,
			alpha : /^[a-z ._-]+$/i,
			alphanum : /^[a-z0-9 ._-]+$/i,
			digit : /^[-+]?[0-9]+$/,
			nodigit : /^[^0-9]+$/,
			number : /^[-+]?\d*\.?\d+$/,
			email : /^([a-zA-Z0-9_\.\-\+%])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/,
			image : /.(jpg|jpeg|png|gif|bmp)$/i,
			phone : /^\+{0,1}[0-9 \(\)\.\-]+$/, // alternate regex : /^[\d\s ().-]+$/,/^((\+\d{1,3}(-| )?\(?\d\)?(-| )?\d{1,5})|(\(?\d{2,6}\)?))(-| )?(\d{3,4})(-| )?(\d{4})(( x| ext)\d{1,5}){0,1}$/
			phone_inter : /^\+{0,1}[0-9 \(\)\.\-]+$/,
			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.groups = {};

			//internalization
			if (typeof(formcheckLanguage) != 'undefined') this.options.alerts = $merge(this.options.alerts, formcheckLanguage);

			this.setOptions(options);

			this.form.setProperty('action',
				this.options.submitAction || this.form.getProperty('action') || 'post');

			this.form.setProperty('method',
				this.options.submitMethod || this.form.getProperty('method') || '');

			this.validations=[];
			this.errors=[];
			
			this.alreadyIndicated = false;
			this.firstError = false;

			$H(this.options.regexp).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));

			if (this.options.display.indicateErrorsInit) {
				this.validations.each(function(el) {
					if(!this.manageError(el,'submit')) this.form.isValid = false;
				}, this);
			}
		}
	},

	/*
	Function: register
		Allows you to declare afterward new fields to the formcheck, to check dynamically loaded fields for example.
		By default it will be the last element to be validated as it's added after others inputs, but you can define a position with second parameter.

	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, 3);

			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, position) {
		el.validation = [];
		el.getProperty("class").split(' ').each(function(classX) {
			if (classX.match(/^validate(\[.+\])$/)) {
				var valid = true;

				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 = validators[i].match(/.+:(.+)$/)[1];
						if (this.form[field].validation.contains('required')) el.validation.push('required');
					}
					if (validators[i].match(/^target:.+/)) {
						el.target = validators[i].match(/^target:(.+)/)[1];
					}
				}

				//we check if group is already registered
				el.isChild = this.isChildType(el, validators);
				if (el.isChild && el.type == 'radio') {
					this.validations.each(function(registeredEl){
						if (registeredEl.name == el.name) valid = false;
					}, this);
				}
				if (el.isChild && el.type == 'checkbox') {
					this.validations.each(function(registeredEl){
						if (registeredEl.groupID == el.groupID) valid = false;
					}, this);
				}

				if (position && position <= this.validations.length) {
					var newValidations = [];
					this.validations.each(function(valider, i){
						if (position == i+1 && valid) {
							newValidations.push(el);
							this.addListener(el);
						}
						newValidations.push(valider);
					}, this);
					this.validations = newValidations;
				} else if (valid) {
					this.validations.push(el);
					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) {
		el.errors = [];

		if (el.validation[0] == 'submit') {
			el.addEvent('click', function(e){
				new Event(e).stop();
				if (this.onSubmit(e)) this.form.submit();
			}.bind(this));
			return true;
		}

		if (!el.isChild) {
			el.addEvent('blur', function() {
				if(!this.fxRunning && (el.element || this.options.display.showErrors == 1) && (this.options.display.checkValueIfEmpty || el.value)) this.manageError(el, 'blur');
			}.bind(this));
		//We manage errors on radio
		} else if(el.isChild && el.type == 'radio') {
			//We get all radio from the same group and add a blur option
			var radioGroup = this.form.getElements('input[name="'+ el.getProperty("name") +'"]');
			radioGroup.each(function(radio){
				radio.addEvent('blur', function(){
					if(!this.fxRunning && (el.element || this.options.display.showErrors == 1) && (this.options.display.checkValueIfEmpty || el.value)) this.manageError(el, 'click');
				}.bind(this));
			},this);
		}
	},

	/*
	Function: manageError
		Private method

		Manage display of errors boxes
	*/
	manageError : function(el, method) {
		var isValid = this.validate(el);
		if (method == 'testonly') return isValid;
		if ((!isValid && el.validation.contains('required')) || (el.value && !isValid)) {
			if(this.options.display.listErrorsAtTop && method == 'submit') this.listErrorsAtTop(el);
			if (this.options.display.indicateErrors == 2 ||this.alreadyIndicated == false || el == this.alreadyIndicated) {
				if(!this.firstError) this.firstError = el;
				this.alreadyIndicated = el;

				if (this.options.display.keepFocusOnError && el == this.firstError) {
					(function(){el.focus()}).delay(10);
				}
				this.addError(el);
				return false;
			}
		} else if ((isValid || (!el.validation.contains('required') && !el.value))) {
			this.removeError(el);
			return true;
		}
		return true;
	},

	/*
	Function: validate
		Private method

		Dispatch check to other methods
	*/
	validate : function(input) {
		var errors=[];
		
		if (this.options.trimValue && input.el.value) input.el.value=input.el.value.trim();

		input.validators.each(function(rule) {
			var ruleArgs=[];
			var ruleMethod=rule;

			if(rule.match(/^.+\[/)) {
				ruleMethod=rule.split('[')[0];
				ruleArgs=eval(rule.match(/^.+(\[.+\])$/)[1].replace(/([A-Z0-9\._-]+)/i, "'$1'"));
			}
			
			if (this.regex.contains(ruleMethod) && input.el.get('tag')!="select") {
				this.validateRegex(input.el, ruleMethod, ruleArgs).each(function(error) {
					errors.push(error);
				});
			}
			
			if (ruleMethod == 'required' && (input.el.get('tag') == "select" || input.el.type == "checkbox")) {
				this.simpleValidate(input.el).each(function(error) {
					errors.push(error);
				});
			}
			
				/*if (rule.match(/confirm:.+/)) {
					ruleArgs = [rule.match(/.+:(.+)$/)[1]];
					if (this.validateConfirm(el, ruleArgs) == false) {
						el.isOk = false;
					}
				}
				if (rule.match(/differs:.+/)) {
					ruleArgs = [rule.match(/.+:(.+)$/)[1]];
					if (this.validateDiffers(el, ruleArgs) == false) {
						el.isOk = false;
					}
				}
				if (ruleMethod == 'words') {
					if (this.validateWords(el, ruleArgs) == 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);
		
		return errors;
	},

	/*
	Function: simpleValidate
		Private method

		Perform simple check for select fields and checkboxes
	*/
	simpleValidate : function(el) {
		var errors=[];
		
		if(el.get('tag') == 'select'){
			if(!el.multiple) {
				if(el.selectedIndex <= 0) {
					errors.push(this.options.alerts.select);
				}
			} else {
				var selected = false;
				el.getChildren('option').each(function(el){
					if(el.selected) selected = true;
				});

				if(!selected){
					errors.push(this.options.alerts.select_multiple);
				}
			}
		} else if (el.type == "checkbox" && el.checked == false) {
			errors.push(this.options.alerts.checkbox);
		}
		
		return errors;
	},

	/*
	Function: validateRegex
		Private method

		Perform regex validations
	*/
	validateRegex : function(el, ruleMethod, ruleArgs) {
		var errors=[];
		
		var msg = "";
		
		if (ruleMethod=='length' && ruleArgs[1]) {
			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 ((ruleMethod == 'digit' || ruleMethod == 'number') && ruleArgs[1]) {
			var valueres, regres = true;
			
			if (!this.options.regexp[ruleMethod].test(el.value)) {
				errors.push(this.options.alerts[ruleMethod]);
				regres = false;
			}
			
			if (ruleArgs[1] == -1) {
				valueres = ( el.value.toFloat() >= ruleArgs[0].toFloat() );
				msg = this.options.alerts.digitmin.replace("%0",ruleArgs[0]);
			} else {
				valueres = ( el.value.toFloat() >= ruleArgs[0].toFloat() && el.value.toFloat() <= ruleArgs[1].toFloat() );
				msg = this.options.alerts.digitltd.replace("%0",ruleArgs[0]).replace("%1",ruleArgs[1]);
			}
			
			if (regres == false || valueres == false) {
				errors.push(msg);
				return false;
			}
		} else if (this.options.regexp[ruleMethod].test(el.value) == false)  {
			errors.push(msg);
			return errors;
		}
		
		return errors;
	},

	/*
	Function: validateConfirm
		Private method

		Perform confirm validations
	*/
	validateConfirm: function(el,ruleArgs) {
		var confirm = ruleArgs[0];
		if(el.value != this.form[confirm].value){
			var msg = ( this.options.display.titlesInsteadNames ) ?
				this.options.alerts.confirm.replace("%0",this.form[confirm].getProperty('title')) :
				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){
			var msg = ( this.options.display.titlesInsteadNames ) ?
				this.options.alerts.differs.replace("%0",this.form[differs].getProperty('title')) :
				this.options.alerts.differs.replace("%0",differs);
			el.errors.push(msg);
			return false;
		}
		return true;
	},

	/*
	Function: validateWords
		Private method

		Perform word count validation
	*/
	validateWords: function(el,ruleArgs) {
		var min = ruleArgs[0];
		var max = ruleArgs[1];

		var words = el.value.replace(/[ \t\v\n\r\f\p]/m, ' ').replace(/[,.;:]/g, ' ').clean().split(' ');

		if(max == -1) {
			if(words.length < min) {
				el.errors.push(this.options.alerts.words_min.replace("%0", min).replace("%1", words.length));
				return false;
			}
		} else {
			if(min > 0)	{
				if(words.length < min || words.length > max) {
					el.errors.push(this.options.alerts.words_range.replace("%0", min).replace("%1", max).replace("%2", words.length));
					return false;
				}
			} else {
				if(words.length > max) {
					el.errors.push(this.options.alerts.words_max.replace("%0", max).replace("%1", words.length));
					return false;
				}
			}
		}
		return true;
	},


	/*
	Function: isFormValid
		public method

		Determine if the form is valid

		Return true or false
	*/
    isFormValid: function() {
		this.form.isValid = true;
		this.validations.each(function(el) {
			var validation = this.manageError(el,'testonly');
			if(!validation) this.form.isValid = false;
		}, this);
		return this.form.isValid;
	},

	/*
	Function: isChildType
		Private method

		Determine if the field is a group of radio, of checkboxes or not.
	*/
	isChildType: function(el, validators) {
		var validator;
		if($defined(el.type) && el.type == 'radio') {
			return true;
		} else if(validator = validators.join().match(/group(\[.*\])/)) {
			var group = eval(validator[1]);
			this.groups[group[0]] = this.groups[group[0]] || [];
			this.groups[group[0]][0] = this.groups[group[0]][0] || [];
			this.groups[group[0]][1] = group[1] || this.groups[group[0]][1] || 1;
			this.groups[group[0]][0].push(el);
			el.groupID = group[0];
			return true;
		}
		return false;
	},

	/*
	Function: validateGroup
		Private method

		Perform radios validations
	*/
	validateGroup : function(el) {
		el.errors = [];
		if(el.type == 'radio') {
			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;
			}
		// we have group of checkboxes
		} else if(el.type == 'checkbox') {
			//we get length of checked elements
			var checked = 0;
			this.groups[el.groupID][0].each(function(groupEl){
				if(groupEl.checked) checked++;
			});
			if(checked >= this.groups[el.groupID][1]) {
				return true;
			} else {
				( this.groups[el.groupID][0].length > 1 ) ?
					el.errors.push(this.options.alerts.checkboxes_group.replace('%0', this.groups[el.groupID][1])) :
					el.errors.push(this.options.alerts.checkbox);
				return false;
			}
		// we have unmanaged type
		} else {
			return false;
		}
	},

	/*
	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);
			}
		}
		window.fireEvent('resize');
	},

	/*
	Function: addError
		Private method

		Add error message
	*/
	showError : function(input, errors) {
		var tip=this.fetchTip(errors);
		tip.injectBefore(input.el);
		
		tip.setStyles({
			position: 'absolute'
		});
		
		var dim=tip.getDimensions();
		
		tip.setStyles({
			'width': dim.width,
			'margin-left': input.el.getSize().x+this.options.display.tipsOffsetX,
			'margin-top': -dim.height
		});
		
		if(this.options.display.closeTipsButton) {
			tip.getElements('a.close').addEvent('click', function(){
				this.destroyError(input);
			}.bind(this));
		}
		
		tip.fade('hide');
		tip.fade(1);
		
		this.errors.push({
			input: input,
			tip: tip
		});
		
		/*if(!obj.element && this.options.display.indicateErrors != 0) {
			if (this.options.display.errorsLocation == 1) {
				var pos = (this.options.display.tipsPosition == 'left') ? coord.left : coord.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 tips = this.createTips(errors).injectInside(obj.element);
				if(this.options.display.closeTipsButton) {
					tips.getElements('a.close').addEvent('mouseup', function(){
						this.removeError(obj, 'tip');
					}.bind(this));
				}
				obj.element.setStyle('top', coord.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 && !obj.isChild){
			obj.addClass(this.options.fieldErrorClass);
			obj.element = obj.element || true;
		}*/

	},
	
	destroyError: function(input, forced) {
		this.errors=this.errors.filter(function(error) {
			if (error.input==input) {
				if (forced) {
					error.tip.destroy();
				} else {
					error.tip.fade(0);
					error.tip.get('tween').addEvent('complete', function() {
						error.tip.destroy();
					});
				}
				
				return false;
			} else {
				return true;
			}
		}, this);
	},

	/*
	Function: addPositionEvent

		Update tips position after a browser resize
	*/
	addPositionEvent : function(obj) {
		if(this.options.display.replaceTipsEffect) {
			obj.event = function(){
				var coord = obj.target ? $(obj.target).getCoordinates() : obj.getCoordinates();
				new Fx.Morph(obj.element, {
					'duration' : this.options.display.fadeDuration
				}).start({
					'left':[obj.element.getStyle('left'), coord.right + this.options.display.tipsOffsetX],
					'top':[obj.element.getStyle('top'), coord.top - obj.element.getCoordinates().height + this.options.display.tipsOffsetY]
				});
			}.bind(this);

		} else {
			obj.event = function(){
				var coord = obj.target ? $(obj.target).getCoordinates() : obj.getCoordinates();
				obj.element.setStyles({
					'left':coord.right + this.options.display.tipsOffsetX,
					'top':coord.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, method) {
		if ((this.options.display.addClassErrorToField && !obj.isChild && this.options.display.removeClassErrorOnTipClosure) || (this.options.display.addClassErrorToField && !obj.isChild && !this.options.display.removeClassErrorOnTipClosure && method != 'tip'))
			obj.removeClass(this.options.fieldErrorClass);

		if (!obj.element) return;
		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);
		}
	},

	/*
	Function: focusOnError
		Private method

		Create set the focus to the first field with an error if needed
	*/
	focusOnError : function (input) {
		if (!this.isScrolling) {
			var dest=input.el.getCoordinates().top-70;
			this.isScrolling = true;

			if (window.getScroll().y != dest) {
				new Fx.Scroll(window, {
					onComplete : function() {
						this.isScrolling = false;
						if (input.el.getProperty('type') != 'hidden') input.el.focus();
					}.bind(this)
				}).start(0,dest);
			} else {
				this.isScrolling = false;
				input.el.focus();
			}
		}
	},

	/*
	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
	*/
	fetchTip : function(txt) {
		var errs = [];
		txt.each(function(html) {
			errs.push(new Element('p').set('html', html));
		});
		
		var div = new Element('div', {'class' : 'fc-tbx'});

		var table = new Element('table').injectInside(div);
			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);
						errs.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 div;
	},

	getValidators: function(el) {
		var validators=[];
		
		el.getProperty("class").split(' ').each(function(classX) {
			if (classX.match(/^validate(\[.+\])$/)) {
				var cssvalidators= eval(classX.match(/^validate(\[.+\])$/)[1]);

				for(var i=0; i<cssvalidators.length; i++) {
					validators.push(cssvalidators[i]);
					
					/*if (validators[i].match(/^confirm:/)) {
						var field=validators[i].match(/.+:(.+)$/)[1];
						if (this.form[field].validation.contains('required')) el.validation.push('required');
					}
					if (validators[i].match(/^target:.+/)) {
						el.target = validators[i].match(/^target:(.+)/)[1];
					}*/
				}

				//we check if group is already registered
				/*el.isChild = this.isChildType(el, validators);
				if (el.isChild && el.type == 'radio') {
					this.validations.each(function(registeredEl){
						if (registeredEl.name == el.name) valid = false;
					}, this);
				}
				if (el.isChild && el.type == 'checkbox') {
					this.validations.each(function(registeredEl){
						if (registeredEl.groupID == el.groupID) valid = false;
					}, this);
				}

				if (position && position <= this.validations.length) {
					var newValidations = [];
					this.validations.each(function(valider, i){
						if (position == i+1 && valid) {
							newValidations.push(el);
							this.addListener(el);
						}
						newValidations.push(valider);
					}, this);
					this.validations = newValidations;
				} else if (valid) {
					this.validations.push(el);
					this.addListener(el);
				}*/
			}
		}, this);
		
		return validators;
	},
	
	getInputs: function() {
		var inputs=[];
		
		this.form.getElements("*[class*=validate]").each(function(el) {
			if (el.isVisible()) {
				inputs.push({
					el:	el,
					validators: this.getValidators(el)
				});
			}
		}, this);
		
		return inputs;
	},
	
	submitByAjax: function() {
		this.fireEvent('ajaxRequest');
		
		new Request({
			url: this.form.action,
			method: this.form.method,
			data : this.form.toQueryString(),
			evalScripts: this.options.ajaxEvalScripts,
			onFailure: function(instance){
				this.fireEvent('ajaxFailure', instance);
			}.bind(this),
			onComplete: function(instance){
				this.fireEvent('ajaxComplete', instance);
			}.bind(this),
			onSuccess: function(result){
				this.fireEvent('ajaxSuccess', result);
				if(this.options.ajaxResponseDiv) $(this.options.ajaxResponseDiv).set('html',result);
			}.bind(this)
		}).send();
		
		return false;
	},
	
	onSubmit: function(event) {
		var inputs=this.getInputs();
		
		var errors=this.errors;
		errors.each(function(error) {
			this.destroyError(error.input, true);
		}, this);
		
		this.fireEvent('onSubmit');

		var iserror=false;
		
		inputs.some(function(input) {
			var errors=this.validate(input);
			
			if (errors.length>0) {
				this.showError(input, errors);
				this.focusOnError(input);
				
				iserror=true;
				
				return true;
			}

			return false;		
		}, this);
		
		if (iserror) {
			this.fireEvent('validateFailure');			
		} else {
			this.fireEvent('validateSuccess');
			
			if (this.options.submitByAjax) {
				return this.submitByAjax();	
			}
			
			return true;
		}
		
		return false;
	}
});
