/*
	About:
		based on the formcheck2.js v.1.0 for mootools v1.1 - 06 / 2007
		by Floor SA (http://www.floor.ch) MIT-style license
		Created by Luca Pillonel and David Mignot, last modified by Luca Pillonel 07.09.07
		Modified by Luciano Amodio (http://www.lucianoamodio.it), last modified by Luciano Amodio 07.16.07
*/

var FormCheck = new Class(
{
	options :
	{
		tips_class				: 'tipsbox',			//tips error class
		error_class				: 'error_f',			//div error class
		restrictionTips_class	: 'restrictionTips',	// restriction tips class
		disableTips_class		: 'disableTips',		// disable tips class
		display : 
		{
			showRestrictions	: false,		// false: no, true:  yes
			showButtons			: false,		// false: no, true:  yes
			showErrors 			: 1,			// 0 : onSubmit, 1 : onSubmit & onBlur
			errorsLocation 		: 2,			//ok 0 : no, 1 : tips, 2 : after
			indicateErrors 		: 2,			//ok 0 :  none, 1 : one, 2 : all
			tipsOffsetLeft 		: 200,			//ok Left position of the tips box (margin-left)
			tipsOffsetTop 		: 10,			//ok Top position of the tips box (margin-Top)
			listErrorsAtTop 	: false,		//ok list all errors at the top of the form
			scrollToFirst 		: false,		//ok Smooth scroll the page to first error
			fadeDuration 		: 300			//ok Transition duration
		},
		alerts : 
		{
			required: "This above field is a required value",
			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",
			digitmin: "The number must be at least %0",
			digitltd: "The value must be between %0 and %1",
			number: "Please enter a valid number.",
			email: "Please enter a valid email address.",
			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: "The length is incorrect, it must be between %0 and %1",
			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) 
	{
		this.form = $(form);
		this.form.isValid = true;
		this.regex = ['length'];
		this.setOptions(options);
		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)
		
		if (this.options.display.showErrors == 1 && this.options.display.showButtons == true)
		{
			var removeTips = new Element ('input', 
			{
				'id' : 'removeTips',
				'class' : this.options.disableTips_class+' button',
			 	'type': 'button',
				'value': 'remove tips',
				events: {
					click: function()
					{
						this.form.getElements("*[class*=validate]").each(function(el)
						{ if($defined(el.element)) this._removeError(el);}, this);
					}.bind(this)
				}
			}).injectTop(this.form); //TODO: find a way to chose a location where place this
			var disableTips = new Element ('input', 
			{
				'id' : 'disableTips',
				'class' : this.options.disableTips_class+' button',
			 	'type': 'button',
				'value': 'disable tips',
				events: {
					click: function()
					{
						this.form.getElements("*[class*=validate]").each(function(el)
						{el.removeEvents()}, this);
					}.bind(this)
				}
			}).injectTop(this.form); //TODO: find a way to chose a location where place this
		}
		
		// check if the el have the validate class
		// push it's validator elements in the el
		// pass the el to the _register method
		this.form.getElements("*[class*=validate]").each(function(el) 
		{
			el.validation = [];
			var classes = el.getProperty("class").split(' ');
			classes.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]); }
					this._register(el);
				}
			}, this);
		}, this);
		
		// call the _onSubmit method on the onSubmit event
		this.form.addEvents({ "submit": this._onSubmit.bind(this) });
	},
	
	/*
	Function: _register
		Private method
		
		Add listener on fields
	*/
	_register : function(el)
	{
		this.validations.push(el);
		el.errors = [];

		// check if el is no a child
		if (this._isChildType(el) == false)
		{
			// create the restriction rules passing the el to the _manageRestrictions method
			if(this.options.display.showRestrictions) this._manageRestrictions(el);
			
			// if is set to display errors on blur, set the relative event
			if (this.options.display.showErrors == 1)
				el.addEvents({ blur: function() { this._manageError(el, 'blur'); }.bind(this) });
			el.addEvents({	focus: function() {	if($defined(el.element)) this._removeError(el); }.bind(this) });
		}
	},
	
	/*
	Function: _isChildType
		Private method
		
		Determine if the field is a group of radio or not.
	*/
	_isChildType: function(el)
	{
		var elType = el.type.toLowerCase();
		if((elType == "radio")) return true;
		return false;
	},
	
	/*
	Function: _manageRestrictions
		Private method
		
		Manage display of restrictions rules
	*/
	_manageRestrictions : function(obj)
	{
		// create the rule set of the passed element
		this._checkRules(obj);
		if(!obj.ruleElement)
		{
			obj.ruleElement = new Element('div', {'id' : 'restrictionTips' + obj.name, 'class' : this.options.restrictionTips_class});
			obj.ruleElement.injectTop(obj.getParent()); // TODO: give the option to chose where to put it
		}
		var rulesTxt = '';
		obj.rules.each(function(rule, n) { (n<1) ? rulesTxt += rule : rulesTxt += ', '+rule; });
		new Element('code').setHTML(rulesTxt).injectInside(obj.ruleElement);
		
		// NOTE: I've not understand why is empty the obj.element at this point in the _manageError methods
		//obj.ruleElement.empty();
	},
	
	/*
	Function: _checkRules
		Private method
		
		Dispatch check to other methods
	*/
	_checkRules : function(el) 
	{
		el.rules = [];
		var ruleArgs = [];
		el.validation.each(function(rule) 
		{
			// this check is unnecessary until the method is called after the check in the _register method
			if(this._isChildType(el)) 
				el.rules.push(this.options.alerts.radios);
			else 
			{
				if(rule.match(/^.+\[/)) //CHECK: why not "/^.+\[.+\]/"
				{ 
					var ruleMethod = rule.split('[')[0];
					// CHECK: Can will be other characters to include?
					var ruleArgs = eval(rule.match(/^.+(\[.+\])$/)[1].replace(/\b[a-z_0-9][a-z_0-9]+/i, "'$&'"));
					
				} else var ruleMethod = rule;
				if (ruleMethod == 'length' || ruleMethod == 'digit')
				{
					if(ruleArgs)
					{
						if (ruleMethod == 'length' && ruleArgs[1])
							if (ruleArgs[1] == -1)
								el.rules.push(this.options.alerts.lengthmin.replace("%0",ruleArgs[0]));
							else
								el.rules.push(this.options.alerts.length.replace("%0",ruleArgs[0]).replace("%1",ruleArgs[1]));
						else if (ruleMethod == 'length' && ruleArgs[0])
							el.rules.push(this.options.alerts.lengthmax.replace("%0",ruleArgs[0]));
						
						if (ruleMethod == 'digit')
							if (ruleArgs[1] == -1)
								el.rules.push(this.options.alerts.digitmin.replace("%0",ruleArgs[0]));
							else
								el.rules.push(this.options.alerts.digitltd.replace("%0",ruleArgs[0]).replace("%1",ruleArgs[1]));
					}
				}
				else if (ruleMethod == 'confirm') el.rules.push(this.options.alerts.confirm.replace("%0",$(ruleArgs[0]).getParent().getElementsByTagName('label')[0].innerHTML));
				else if (ruleMethod == 'differs') el.rules.push(this.options.alerts.differs.replace("%0",$(ruleArgs[0]).getParent().getElementsByTagName('label')[0].innerHTML));
				else if (el.getTag() == 'select') el.rules.push(this.options.alerts.select);
				else if (el.getTag() == "input" && el.type == "checkbox") el.rules.push(this.options.alerts.checkbox);
				else if (this.options.alerts[ruleMethod]) el.rules.push(this.options.alerts[ruleMethod]);
				
			}				
		}, this);
	},
	
	/*
	Function: _manageError
		Private method
		
		Manage display of errors boxes
	*/
	_manageError : function(el, method) {
		var isValid = this._validate(el);
		if (((!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)
				{
					this._addError(el);
					return false;
				}
		} else if ((isValid || (!el.validation.contains('required') && !el.value)) && el.element) {
			this._removeError(el);
			return true;
		}
		return true;
	},
	
	/*
	Function: _validate
		Private method
		
		Dispatch check to other methods
	*/
	_validate : function(el) {
		el.errors = [];
		el.isOk = true;
		//On valide l'ŽlŽment qui n'est pas un radio ni checkbox
		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];
					// CHECK: Can will be other characters to include?
					var ruleArgs = eval(rule.match(/^.+(\[.+\])$/)[1].replace(/\b[a-z_0-9][a-z_0-9]+/i, "'$&'"));
				} else var ruleMethod = rule;
				
				if (this.regex.contains(ruleMethod)) {
					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.getTag() == "select" || (el.getTag() == "input" && el.type == "checkbox")) {
					if (this._simpleValidate(el) == false) {
						el.isOk = false;
					}
				}
			}				
		}, this);
		
		if (el.isOk == true) return true;
		return false;
	},
	
	/*
	Function: _simpleValidate
		Private method
		
		Perform simple check for select fields and checkboxes
	*/
	_simpleValidate : function(el) {
		if (el.getTag() == 'select' && (el.value == el.options[0].value)) {
			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("^.{"+ ruleArgs[0] +",}$");
				msg = this.options.alerts.lengthmin.replace("%0",ruleArgs[0]);
			} else {
				this.options.regexp.length = new RegExp("^.{"+ ruleArgs[0] +","+ ruleArgs[1] +"}$");
				msg = this.options.alerts.length.replace("%0",ruleArgs[0]).replace("%1",ruleArgs[1]);
			}
		} else if (ruleArgs[0]) {
			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) {
		if (el.validation.contains('required') == false) {
			el.validation.push('required');
		}
		var confirm = ruleArgs[0];
		if(el.value != this.form[confirm].value){
			msg = this.options.alerts.confirm.replace("%0",$(ruleArgs[0]).getParent().getElementsByTagName('label')[0].innerHTML);
			el.errors.push(msg);
			return false;
		}
		return true;
	},
	
	/*
	Function: _validateDiffers
		Private method
		
		Perform differs validations
	*/
	_validateDiffers: function(el,ruleArgs) {
		var confirm = ruleArgs[0];
		if(el.value == this.form[confirm].value){
			msg = this.options.alerts.differs.replace("%0",$(ruleArgs[0]).getParent().getElementsByTagName('label')[0].innerHTML);
			el.errors.push(msg);
			return false;
		}
		return true;
	},
	
	/*
	Function: _validateGroup
		Private method
		
		Perform radios validations
	*/
	_validateGroup : function(el) {
		el.errors = [];
		var nlButtonGroup = this.form[el.getProperty("name")];
		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, method) {
		if(this.options.display.listErrorsAtTop == true && method == 'submit') {
			if ($type(obj) == 'collection') {
				new Element('p').setHTML("<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').setHTML("<span>" + obj.name + " : </span>" + error).injectInside(this.form.element);
					}, this);
				}
			}
		}
	},
	*/
	_listErrorsAtTop : function(obj) {
		if(!this.form.element) {
			 this.form.element = new Element('div', {'id' : 'errorlist', 'class' : this.options.error_class}).injectTop(this.form);
		}
		if ($type(obj) == 'collection') {
			new Element('p').setHTML("<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').setHTML("<span>" + obj.name + " : </span>" + error).injectInside(this.form.element);
				}, this);
			}
		}
	},
	
	/*
	Function: _addError
		Private method
		
		Add error message
	*/
	_addError : function(obj) {
		this.alreadyIndicated = obj;
		if(!this.firstError) this.firstError = obj;
		if(!obj.element) {
			if (!window.ie) var marginLeft = this.options.display.tipsOffsetLeft;
			else var marginLeft = this.options.display.tipsOffsetLeft - 75;
			var marginTop = this.options.display.tipsOffsetTop;
			if (this.options.display.errorsLocation == 1) {
				obj.element = new Element('div', {'id' : 'diverror' + obj.name, 'class' : this.options.tips_class, 'styles' : {'opacity' : 0, 'position' : 'absolute', 'margin-top' : marginTop, 'margin-left' : marginLeft}});
				obj.element.injectInside($E('form'));
			} else {
				obj.element = new Element('div', {'id' : 'diverror' + obj.name, 'class' : this.options.error_class, 'styles' : {'opacity' : 0}});
				
				if (window.webkit) {
					if ($type(obj) == 'object') obj.element.injectAfter(objc[objc.length-1]);
					else obj.element.injectAfter(obj);
				} else {
					if ($type(obj) == 'collection') obj.element.injectAfter(objc[objc.length-1]);
					else obj.element.injectAfter(obj);
				}
			}
		}
		var errorTxt = '';
		obj.errors.each(function(error, n) {
			(n<1) ? errorTxt += error : errorTxt += ', '+error;
		});			
		obj.element.empty();
		if (this.options.display.errorsLocation == 1) {
			var tips = this._makeTips(new Element('p').setHTML(errorTxt)).injectInside(obj.element);
			obj.element.setStyle('top', obj.getCoordinates().top - tips.getCoordinates().height+10);
		} else {
			new Element('p').setHTML(errorTxt).injectInside(obj.element);
		}
		
		if (!window.ie7 && obj.element.getStyle('opacity') == 0)
			new Fx.Styles(obj.element, {'duration' : this.options.display.fadeDuration}).start({'opacity':[1]});
		else
			obj.element.setStyle('opacity', 1);
	},
	
	/*
	Function: _removeError
		Private method
		
		Remove the error display
	*/
	_removeError : function(obj) {
		this.firstError = false;
		this.alreadyIndicated = false;
		obj.errors = [];
		obj.isOK = true;
		if (this.options.display.errorsLocation == 2)
			new Fx.Styles(obj.element, {'duration' : this.options.display.fadeDuration,}).start({ 'height':[0] });
		if (!window.ie7) {
			new Fx.Styles(obj.element, {
				'duration' : this.options.display.fadeDuration,
				'onComplete' : function() {
					if (obj.element) {
						obj.element.remove();
						obj.element = false;
					}
				}.bind(this)
			}).start({ 'opacity':[1,0] });
		} else {
			obj.element.remove();
			obj.element = false;
		}
	},
	
	/*
	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.alreadyIndicated.element && !this.isScrolling) {
			if (this.options.display.errorsLocation == 1) new Fx.Scroll(window, {onComplete : function() {this.isScrolling = false;}.bind(this)}).scrollTo(0,obj.element.getCoordinates().top);
			else if (this.options.display.errorsLocation == 2) new Fx.Scroll(window, {onComplete : function() {this.isScrolling = false;}.bind(this)}).scrollTo(0,obj.getCoordinates().top-30);
			this.isScrolling = true;
			obj.focus();
			this.alreadyFocused = true;
		} else if (this.options.display.scrollToFirst && !this.isScrolling) {
			new Fx.Scroll(window, {onComplete : function() {this.isScrolling = false;}.bind(this)}).scrollTo(0,obj.getCoordinates().top-30);
			this.isScrolling = true;
			obj.focus();
			this.alreadyFocused = true;
		}
	},
	
	/*
	Function: _makeTips
		Private method
		
		Create tips boxes
	*/
	_makeTips : function(txt) {
		var table = new Element('table', {'class' : 'tipsbox'});
			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' : 'tipsbox_top_left'}).injectInside(tr1);
					new Element('td', {'class' : 'tipsbox_top'}).injectInside(tr1);
					new Element('td', {'class' : 'tipsbox_top_right'}).injectInside(tr1);
				var tr2 = new Element('tr').injectInside(tbody);
					new Element('td', {'class' : 'tipsbox_left'}).injectInside(tr2);
					var errors = new Element('td', {'class' : 'tipsbox_inner'}).injectInside(tr2);
					txt.injectInside(errors);

					new Element('td', {'class' : 'tipsbox_right'}).injectInside(tr2);
				var tr3 = new Element('tr').injectInside(tbody);
					new Element('td', {'class' : 'tipsbox_bottom_left'}).injectInside(tr3);
					new Element('td', {'class' : 'tipsbox_mark'}).injectInside(tr3);
					new Element('td', {'class' : 'tipsbox_bottom_right'}).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;
				el.element.remove();
				el.element = false;
			}
		});
		if (this.form.element) this.form.element.empty();
		this.alreadyFocused = false;
		this.firstError = false;
		this.alreadyIndicated = false;
		this.form.isValid = true;
	},
	
	/*
	Function: _onSubmit
		Private method		
		
		Perform check on submit action
	*/
	_onSubmit: function(event) {
		this._reinitialize();

		this.validations.each(function(el) {
			if(!this._manageError(el,'submit')) this.form.isValid = false;
		}, this);
		if(!this.form.isValid) { 
			new Event(event).stop();
			if (this.firstError) this._focusOnError(this.firstError);
			alert('There are some errors on this form.\n\nPlease check all fields with alerts and re-submit.\n')
		} else {
			// nothing yet
			if( (this.form.enqemail.value == '') && (this.form.enqphone.value == '') && (this.form.enqmobile.value == '') ){
				alert('You must provide either a phone, mobile or emaill address to continue')
				new Event(event).stop();
			} else {
				// contact options good, do not stop on LIVE form
				//let it send the email!!!
			}
		}
	}
});
FormCheck.implement(new Options());