/**
 * JSForm
 * @author Charles Demers
 * @version 0.1
 * @requires Core.js
 * @requires jQuery (http://www.jquery.com)
 */

function JSForm(element,opts){
	this.$type = "JSForm";
	
	var sel = jQuery(element);
	if(sel.length !== 0){
		this.form = sel;
	} else {
		throw new Error("[JSForm] '"+element+"' could not be found");
	}
	if(opts){
		this.lang = (opts.lang) ? opts.lang : "fr";
		var breakOnError = (opts.breakOnError) ? opts.breakOnError : false;
		this.submitCallback = (opts.submit) ? opts.submit : null;
	}
	var that = this;
	this.form.submit(function(){
		var ret = that.submit();

		if(ret !== undefined){
			return ret;
		}
	});
	this.inputs = new Hash();
	this.errors = new Hash();
}
JSForm.implement({
	
	addValidation:function(){
		throw new Error('Method not available to parent class.');
	},
	getValue:function(){
		throw new Error('Method not available to parent class.');
	},
	registerInput:function(element,validation,message,setBreak,groupOpts){
		
		var that = this;
		var lastInput;
		
		function addValidations(last){
			var messageSent, willBreak;
			if(validation instanceof Array){
				willBreak = (message != undefined) ? message : that.breakOnError;
				for(var i=0, l=validation.length; i<l; i++){
					messageSent = (validation[i][1]) ? validation[i][1] : {lang:that.lang};
					lastInput.addValidation(validation[i][0],messageSent,willBreak);
				}
			} else {
				messageSent = (message && typeOf(message) == "string") ? message : {lang:that.lang};
				willBreak = (setBreak != undefined) ? setBreak : (typeOf(message) == "boolean") ? message : that.breakOnError;
				lastInput.addValidation(validation,message,willBreak);
			}
		}
		
		if(groupOpts !== undefined){
			this.inputs.add(groupOpts.name,new JSFormInput(jQuery(element)));
			lastInput = this.inputs.get(groupOpts.name);
			addValidations();
		} else {
			jQuery(this.form).find(element+":not(input[type^=submit],input[type^=button],input[type^=reset]):input").each(function(){
				lastInput = that.addInput(jQuery(this));
				addValidations();
			});
		}
	},
	registerGroup:function(name,els,validation,message,setBreak){
		this.registerInput(els,validation,message,setBreak,{name:name});
	},
	addInput:function(element){
		
		var name = element.attr('name');
		var type = element.attr('type');
		if(type == "radio"){
			if(validation == "required"){
				if(this.inputs.hasKey(name) === false){
					this.inputs.add(name, new JSFormRadioButton(element));
				} else {
					this.inputs.get(name).addInput(element);
				}
			}
		} else {
			if(this.inputs.hasKey(name) === false){
				this.inputs.add(name,new JSFormInput(element));
			}
		}
		return this.inputs.get(name);
	},
	getErrors:function(){
		var that = this;
		this.inputs.forEach(function(el,name){
			that.errors.add(name,el.getErrors());
		});
		return this.errors.getClean();
	},
	getErrorsForName:function(name){
		return this.inputs.get(name).errors;
	},
	getInput:function(name){
		return this.inputs.get(name);
	},
	validate:function(){
		var ret = true;
		this.inputs.forEach(function(el,name){
			if(el.validate() === false){
				ret = false;
			}
		});
		return ret;
	},
	submit:function(){
		this.errors.empty();
		var isValid = this.validate();
		var errors = this.getErrors();
		
		if(this.submitCallback !== ""){
			try{
				var ret = this.submitCallback.call(this,isValid,errors);
				if(ret !== undefined){
					return ret;
				}
			} catch(e){
				alert("[JSForm submitHandler] "+e);
				return false;
			}
		}
		if(isValid === false){
			return false;
		}
	}
});


function JSFormInput(element){
	this.$type = "JSFormInput";
	
	var sel = jQuery(element);
	if(sel.length >= 1){
		this.input = sel;
	} else {
		throw new Error("[JSFormInput] '"+element+"' could not be found;");
	}
	this.validations = [];
	this.errors = [];
	return this;
}
JSFormInput.implement({
	addValidation:function(validation,message,breakOnError){
		if(typeOf(validation) == "string" && JSFormValidationRegExp.hasKey(validation)){
			message = (typeOf(message) == "object" && message.lang) ? JSFormErrors.get(message.lang).get(validation) : message;
			this.validations.push([JSFormValidationRegExp.get(validation), message]);
		} else if(typeOf(validation) == "object"){
			message = (typeOf(message) == "object" && message.lang) ? JSFormErrors.get(message.lang).get(validation) : message;
			if(JSFormValidationFunctions.hasKey(validation.fn)){
				this.validations.push([JSFormValidationFunctions.get(validation.fn),validation.args,message]);
			} else {
				this.validations.push([validation.fn,validation.args,message]);
			}
		} else {
			if(typeof message == undefined){
				throw new Error("[JSFormInput addValidation] custom validation requires specifying a custom error message");
			}
			if(typeOf(validation) == "regexp"){
				this.validations.push([validation, message]);
			} else if(typeOf(validation) == "function"){
				this.validations.push([validation, message]);
			}
		}
		this.breakOnError = (breakOnError) ? breakOnError : false;
	},
	getErrors:function(){
		return this.errors;
	},
	getValue:function(){
		if(jQuery(this.input).attr("type") == "checkbox"){
			return (jQuery(this.input).attr("checked") === true) ? "checked" : "";
		}
		var ret = "";
		jQuery(this.input).each(function(){
			ret += jQuery(this).val();
		});
		return ret;
	},
	validate:function(){
		this.errors.empty();
		var value = this.getValue();
		var vals = this.validations;
		
		var i;
		if(value === ""){
			for(i=0, l=vals.length; i<l; i++){
				if(vals[i][0] == "required"){
					this.errors.push(vals[i].getLast());
					return false;
				}
			}
			return true;
		} else {
			
			for(i=0, l=vals.length; i<l; i++){
				// throws error if default value
				if(vals[i][0] == "required"){
					if(this.input.get(0).defaultValue == value){
						this.errors.push(vals[i].getLast());
						return false;
					}
				}
				if(this.input.get(0).defaultValue != value){
					if(typeOf(vals[i][0]) == "regexp"){
						
						if(vals[i][0].test(value) === false){
							this.errors.push(vals[i].getLast());
							if(this.breakOnError){ return false; }
						}
					} else if(typeOf(vals[i][0]) == "function"){
						var ret;
						ret = false;
						if(typeOf(vals[i][1]) == "array"){
							var args = vals[i][1].clone();
							args.splice(0,0,value);
							ret = vals[i][0].apply(this,args);
						} else {
							ret = vals[i][0].call(this,value);
						}
						if(ret === false){
							this.errors.push(vals[i].getLast());
							if(this.breakOnError){ return false; }
						}
					}
				}
			}
			return (this.errors.length === 0);
		}
	}
});


function JSFormRadioButton(element){
	this.$type = "JSFormRadioButton";
	this.els = [jQuery(element)];
	
	this.validations = {
		regexp:[]
	};
	this.errors = [];
}
JSFormRadioButton.prototype = {
	addInput:function(element){
		this.els.push(jQuery(element));
	},
	addValidation:function(validation,message,breakOnError){
		this.validations.regexp.push([validation,message]);
	},
	getErrors:function(){
		return this.errors;
	},
	getValue:function(){
		return $(this.els[0]+":checked").val();
	},
	validate:function(){
		this.errors.empty();
		var value = this.getValue();
		if(value == undefined){
			this.errors.push(this.validations.regexp[0][1]);	
		}
		return (this.errors.length === 0);
	}
};


var JSFormValidationRegExp = new Hash({
	required : "required",
	email : /^[a-z0-9._%+\-]+@[a-z0-9\.\-]+\.[a-z]{2,4}$/,
	url : /^(([\w]+:)?\/\/)?(([\d\w]|%[a-fA-f\d]{2,2})+(:([\d\w]|%[a-fA-f\d]{2,2})+)?@)?([\d\w][-\d\w]{0,253}[\d\w]\.)+[\w]{2,4}(:[\d]+)?(\/([-+_~.\d\w]|%[a-fA-f\d]{2,2})*)*(\?(&?([-+_~.\d\w]|%[a-fA-f\d]{2,2})=?)*)?(#([-+_~.\d\w]|%[a-fA-f\d]{2,2})*)?$/,
	phone : /^(([0-9]{1})*[- .(]*([0-9]{3})[- .)]*[0-9]{3}[- .]*[0-9]{4})+$/,
	zip : /^[ABCEGHJKLMNPRSTVXY][0-9][A-Z](\ )?[0-9][A-Z][0-9]$/i,
	usZip : /^[0-9]{5}(?:-[0-9]{4})?$/,
	date : /^(19|20)?[0-9]{2}[- \/\.](0?[1-9]|1[012])[- \/\.](0?[1-9]|[12][0-9]|3[01])$/,
	creditCard : /^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6011[0-9]{12}|3(?:0[0-5]|[68][0-9])[0-9]{11}|3[47][0-9]{13})$/,
	alphaNumeric : /^[a-zA-Z0-9]+$/,
	alphaNumericWithSpaces : /^[a-zA-Z0-9\ ]+$/,
	letters : /^[a-zA-Z]+$/,
	digits : /^[0-9]+$/
});


var JSFormValidationFunctions = new Hash({
	isBetween:function(value,min,max){
		if(value >= min && value <= max){ return true; }
		return false;
	},
	isNotDefault:function(value){
		return (this.input.get(0).defaultValue != value);
	}
});


var JSFormErrors = new Hash({
	fr : new Hash({
		required	: "Ce champ ne peux être vide.",
		digits		: "Ce champ n'accepte que des caractères numériques.",
		email		: "Veuillez entrer un courriel valide.",
		url			: "Veuillez entrer un url valide.",
		phone		: "Veuillez entrer un numéro de téléphone valide.",
		zip			: "Veuillez entrer un code postal valide.",
		usZip		: "Veuillez entrer un code postal valide.",
		date 		: "Veuillez entrer une date valide."
	}),
	en : new Hash({
		required	: "This field cannot be empty.",
		digits		: "This field only accepts numeric caracters.",
		email		: "Please enter a valid e-mail.",
		url			: "Please enter a valid url.",
		phone		: "Please enter a valid phone number.",
		zip			: "Please enter a valid zip.",
		usZip		: "Please enter a valid zip.",
		date		: "Please enter a valid date."
	})
});


var UIForm = {
	makeSmartInput : function(element){		
		jQuery(element).not("input[type=submit], input[type=button]").blur(function(){
			if(jQuery(this).val() === ""){
				jQuery(this).val(this.defaultValue);
			}
		});
		jQuery(element).not("input[type=submit], input[type=button]").focus(function(){
			if(jQuery(this).val() == this.defaultValue){
				jQuery(this).val("");
			}
		});
	}
};
