/**
 * Form validation plugin for Centrik, given a form validates against rules provided by that form
 * @note this enhances rather than replaces server side form validation
 * @link http://customr.net
 * @copyright Copyright (c) 2009 James Ellis http://customr.net
 * @license Licensed under the MIT license
 * @since 0.1 (17.02.2009)
 * @depends jquery >= 1.3.1
 */

(function($) {
	/**
	 * centrik_validator()
	 * @note call like $('#frmid').centrik_validator(validation data, settings);
	 */
	$.fn.centrik_validator = function(data, settings) {
		//return an instance of the validate handler
		return new $.centrik_validate(this, data, settings);
	};
	
	$.centrik_validate = function(form, data, settings) {
		this.form = form;
		this.data = data;
		this.settings = {
			debug : false,
			showspeed : 150,//'fast',
			hidespeed : 150,//'fast',
			prefix : 'vmessage_',//prefix for form field messages
			msg : 'This field needs to be completed'//fallback message if none defined for element
		};
		//and listen for submission
		this.reset();
		this.init(settings);
	};
		
	$.extend($.centrik_validate, {
		prototype : {
			init : function(settings) {
				this.settings = $.extend({}, this.settings, settings);
				this.queue();
				var t = this;
				$(this.form).bind(
					'submit',
					function() {
						try {
							//trigger validation on elements
							t.reset();
							t.triggerAll();
							if(t.hasErrors()) {
								t.showErrors();
								t.debug('failed');
								return false;
							} else {
								t.debug('submit');
								return (t.settings.debug ? false : true);
							}
						} catch (e) {
							t.debug(e);
							return false;
						}
					}
				);
				return false;
			},
			debug : function(msg) {
				if(this.settings.debug) {
					console.log(msg);
				}
			},
			validationErrors : {
				length:0,
				errors:{}
			},
			reset : function() {
				//reset errors
				this.validationErrors = {length:0,errors:{}};
			},
			isvalid : function() {
				return this.validationErrors.length > 0;
			},
			/**
			 * adderror() adds an error to the stack, attached to the element
			 * @param element the element associated with the error
			 * @todo user configured errors
			 */
			addError : function(element) {
				var msg = this.settings.msg;
				this.validationErrors.errors[element.id] = msg;
				this.validationErrors.length++;
				$(element).addClass('invalid');
			},
			
			clearError :  function(element) {
				$(element).removeClass('invalid');
				delete this.validationErrors.errors[element.id];
			},
			
			/**
			* bind() associates a validation event with a form element
			* @param element the jquery element to be bound to a validation event
			* @todo make the validation string configurable
			*/
			bind : function(element) {
				var t = this;
				//bind a validation event to the form element
				element.bind(
					'validate',
					function(event) {
						//has a value and test
						if(!t.validate(this)) {
							//t.debug($(this).attr('id') + 'failed');
							t.addError(this);
						} else {
							t.clearError(this);
						}
						event.stopPropagation();
					}
				);
			},
			/*
			* queue() given validation and a form, bind validation events to elements in that form
			* @param fdata is a JSON encoded string of data
			* @param frm the frm associated with the validation data
			*/
			queue : function() {
				this.reset();//clear current errors
				this.clearErrors();//show any current validation messages
				var id;
				this.debug('queuing form: ' + $(this.form).attr('id'));
				for(id in this.data) {
					var e = $('#' + id);//element
					e.removeClass('invalid');//clear class on init
					//get the validation data for this element
					if(typeof this.data[id].type != 'undefined' && typeof this.data[id].req != 'undefined') {
						//whether this field is required prior to submission
						this.data[id].req = (this.data[id].req == true ? true : false);
						//if the field has a regex applied
						this.data[id].regex = (this.data[id].regex != null ? this.data[id].regex : false);
						this.debug('binding ' + id);
						e.data('centrik_validationdata', this.data[id]);
						this.bind(e);
					}//end function
				}
				return true;
			},
			hasErrors : function() { return this.isvalid(); },
			/**
			 * triggerAll() trigger all events with validation to validate themselves
			 */
			triggerAll : function() {
				//each element in the form
				var t = this;
				$(this.form).find(':input').each(
					function() {
						//does it have validation data?
						var d = $(this).data('centrik_validationdata');
						if(typeof d != 'undefined') {
							t.debug($(this).attr('id'));
							t.debug(d);
							$(this).trigger('validate');
						}
					}
				);
			},
			validate : function(e) {
				var data = $(e).data('centrik_validationdata');
				if(data) {
					//field validation strings
					var type = data.type;
					var req = data.req;
					var regex = data.regex;
					
					//grab the element type
					//field needs to be validated
					var value = false;
					
					switch(e.nodeName) {
						case 'INPUT':
							switch($(e).attr('type')) {
								case 'text':
								case 'password':
									value = $(e).val();
									break;
								case 'checkbox':
								case 'radio':
									value = $(e).attr('value');
									break;
							}
							break;
						case 'TEXTAREA':
							value = $(e).val();
							break;
						case 'SELECT':
							//note for multiple selects returns an array
							value = $(e).val();
							break;
					}
					if(value === false) {
						//fall through
						return true;
					}
					var str = '';
					//provide basic form validation, extend with regexes
					switch(type) {
						case 'email':
							str = '.*@.+';//basic, use regex for your own
							break;
						case 'alpha':
							str = '[a-zA-Z]+';
							break;
						case 'string':
						case 'file':
							str = '.+';
							break;
						case 'integer':
						case 'int':
							str = '[0-9]+';
							break;
						case 'url':
							str = '[a-z]+:\/\/.+';
							break;
						case 'httpurl':
							str = 'http:\/\/.+';
							break;
						case 'ftpurl':
							str = 'ftp:\/\/.+';
							break;
						case 'float':
						case 'decimal':
							str = '[0-9]*\.?[0-9]*';
							break ;
						case 'regex':
							//field has a defined regex
							if(regex) {
								str = regex;
							}
							break;
						default:
							this.debug('fallthru for value (' + value + ') of type (' + type + ')');
							return true;
							break;
					}
					
					if(str != '') {
						if(!req) {
							str = '(' + str + ')?';
						}
						str = '^' + str + '$';
						var regex = new RegExp(str, 'gm');
						result = regex.test(value);
						if(!result) {
							this.debug(result + ' for value (' + value + ') of type (' + type + ') in element (' + e.id + ')');
						}
						return result;
					} else  {
						throw('Error: empty regular expression provided for type (' + type + ')');
					}
				}
				//fall thru if we cannot validate the element, give to server
				return true;
			},
			clearErrors : function() {
				//clears error messages
				var t = this;
				$(this.form).find('div.vmessage').each (
					function() {
						$(this).empty();
						t.hideMessage(this);
					}
				);
				return true;
			},
			showErrors : function() {
				this.clearErrors();
				var messagebox;
				var messagetext;
				var fld, vfld;
				for(fld in this.validationErrors.errors) {
					msg = '#' + this.settings.prefix + fld;
					if($(msg).length > 0) {
						this.hideMessage(msg);
						$(msg).empty();
						$(msg).html(this.validationErrors.errors[fld]);
						this.showMessage(msg, fld);
					}
				}
				return false;
			},
			positionMessage : function(msg, fld) {
				/*
				var o = $('#' + fld).offset();
				$(msg).css('position', 'absolute');
				$(msg).css('display','block');//(this.settings.showspeed);
				$(msg).css('left', (o.left - $(msg).width()) + 'px');
				$(msg).css('top', o.top + 'px');
				*/
				return false;
			},
			showMessage : function(msg, fld) {
				//position the validation msg near the field
				this.positionMessage(msg,fld);
				$(msg).slideDown(this.settings.showspeed);
			},
			hideMessage : function(msg) {
				$(msg).slideUp(this.settings.hidespeed);
			}
		}
	});
//end
})(jQuery);