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

		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

		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 : /^[\d\s ().-]+$/, // 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.validations = [];
			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 = eval(validators[i].match(/^.+(\[.+\])$/)[1].replace(/([A-Z0-9\._-]+)/i, "'$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){
				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.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(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(el) {
		el.errors = [];
		el.isOk = true;

		//skip validation for disabled fields 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(el.isChild) {
				if (!this.validateGroup(el)) el.isOk = false;
			} else {
				var ruleArgs = [];

				if(rule.match(/target:.+/)) return;

				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 (ruleMethod == 'words') {
					if (this.validateWords(el, ruleArgs) == false) {
						el.isOk = false;
					}
				}
				if (ruleMethod == 'required' && (el.get('tag') == "select" || el.type == "checkbox")) {
					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'){
			if(!el.multiple) {
				if(el.selectedIndex <= 0) {
					el.errors.push(this.options.alerts.select);
					return false;
				}
			} else {
				var selected = false;
				el.getChildren('option').each(function(el){
					if(el.selected)
					selected = true;
				});

				if(!selected){
					el.errors.push(this.options.alerts.select_multiple);
					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) {
				var valueres = ( el.value.toInt() >= ruleArgs[0].toInt() );
				msg = this.options.alerts.digitmin.replace("%0",ruleArgs[0]);
			} else {
				var valueres = ( el.value.toInt() >= ruleArgs[0].toInt() && el.value.toInt() <= ruleArgs[1].toInt() );
				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: 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
	*/
	addError : function(obj) {
		//determine position
		var coord = obj.target ? $(obj.target).getCoordinates() : obj.getCoordinates();

		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 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, '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;
		}

	},

	/*
	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 (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 {
				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;
						if (obj.getProperty('type') != 'hidden') 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
		Reinitialize form before submit check. You can use this also to remove all tips from a form, passing the argument "forced" ( formcheck.reinitialize('forced'); )
	*/
	reinitialize: function(forced) {
		this.validations.each(function(el) {
			if (el.element) {
				el.errors = [];
				el.isOK = true;
				if(this.options.display.flashTips == 1 || forced == 'forced') {
					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') || 'post',
			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.fireEvent('onSubmit');

		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 if(!this.options.submit) {
				new Event(event).stop();
				return false;
			}
			this.fireEvent('validateSuccess');
			return true;
		} else {
			if (event) new Event(event).stop();
			if (this.elementToRemove && this.elementToRemove != this.firstError && this.options.display.indicateErrors == 1) {
				this.removeError(this.elementToRemove);
			}
			this.focusOnError(this.firstError);
			this.fireEvent('validateFailure');
			return false;
		}
	}
});

