/**
 * LBi Form module
 *
 * @module form
 * @version   1.00.091211
 * @requires  jQuery, LBi
 * @author    LBi Lost Boys
 */
LBi.Forms = (function($){

	var Class = LBi.Class;
	var Event = LBi.Event;
	var Dispatcher = LBi.Dispatcher;

	/**
	 * The Forms class augments forms with additional behavior. The settings object
	 * may be used to specify preferences. By default listens to the DOMNodeInserted and -removed 
	 * events as fired by the DOM mediator, to automatically augment new forms inserted via ajax.
	 * Use formSelector settings to assign forms to specific instances when more than one is used.
	 * 
	 * @class LBi.Forms
	 * @extends LBi.DOMListener
	 * @constructor
	 * @param {Object} settings Settings object, see LBi.Forms.Defaults.
	 * @return {Forms} forms instance
	 */
	var Forms = Class.extend(
		LBi.DOMListener, 

		function(settings){
			this.settings = $.extend({}, Forms.Defaults, settings);
			this.parseNode(document);
		},{

		/**
		 * Parses the given node for forms and inputs, and augments them based on settings.
		 * This method is automatically called by LBi.Form instances.
		 * 
		 * @param {Node} node 
		 */
		parseNode:function(node){
			var set = this.settings;
			var forms = $(set.formSelector, node);
			
			if(forms.length > 0){				
				if(set.captureSubmit){
					Dispatcher.capture(Event.SUBMIT, forms);
				}

				if(set.captureChange){
					var inputs = $('input,select,textarea', forms);
					Dispatcher.capture(Event.CHANGE, inputs);
				}
				
				if(set.replaceInputs){
					this.replaceInputs(forms);
				}
				
				if(set.hintValues){
					this.hintValues(forms);
				}
			}
		},

		/**
		 * Automatically called via the DOMNodeInserted event. Passes the new node to the main parse function.
		 *
		 * @param {Event} e The event object
		 */
		nodeInserted:function(e){
			if(this.settings.ajaxEnabled){
				this.parseNode(e.target);
			}
		},

		/**
		 * Automatically called via the DOMNodeRemoved event. 
		 *
		 * @param {Event} e The event object
		 */
		nodeRemoved:function(e){
			var set = this.settings;
			if(set.captureSubmit){
				var forms = $(set.formSelector, e.target);
				forms.unbind();
			}

			if(set.captureChange){
				var inputs = $('input,select,textarea', e.target);
				inputs.unbind();
			}
		},

		/**
		 * Binds focus and blur events to inputs encountered in the given form(s). 
		 *
		 * @param {Node|NodeList|jQuery} forms The node(s) to search for inputs. 
		 */
		hintValues:function(forms){
			var REG_TEXT = /text|pass/i;
			var inputs = $(this.settings.hintSelector, forms);
			var focusInput = this.focusInput.bind(this);
			var blurInput = this.blurInput.bind(this);
			inputs.each(function(){
				if(!REG_TEXT.test(this.type)){
					return;
				}

				var jInput = $(this);
				jInput.bind(Event.FOCUS, focusInput);
				jInput.bind(Event.BLUR, blurInput);
			});

			inputs.trigger(Event.BLUR);
		},

		/**
		 * Focus handler for hinted inputs. Conditionally clears the input's value and removes 
		 * the hintClass from the input.
		 * 
		 * @param {Event} e The event object
		 */
		focusInput:function(e){
			var input = e.target;
			if(input.value === input.title){
				input.value = '';
				$(input).removeClass(this.settings.hintClass);
			}
		},

		/**
		 * Blur handler for hinted inputs. Conditionally sets the input's value to its title
		 * and adds the hintClass to the input.
		 * 
		 * @param {Event} e The event object
		 */
		blurInput:function(e){
			var input = e.target;
			if(input.value === input.title || input.value === ''){
				input.value = input.title;
				$(input).addClass(this.settings.hintClass);
			}
		},

		/**
		 * Replaces submit and reset buttons by styled links based on the template provided
		 * to the settings object. 
		 * 
		 * @param {Node|NodeList|jQuery} forms The node(s) to search for inputs.
		 */
		replaceInputs:function(forms){
			var REG_BUTTON = /submit|reset/i;
			var REG_SUBMIT = /submit/i;
			var set = this.settings;
			var template = set.buttonTemplate;
			var replaced = set.replacedClass;
			var simulate = set.simulateClick;
			var inputs = $(set.buttonSelector, forms);

			inputs.each(function(){
				if(!REG_BUTTON.test(this.type)){
					return;
				}

				var input = this;
				var form = input.form;
				var jInput = $(input);
				var jButton = $(
					template.replace(
						/\$([a-z]+)/mig, 
						function(match, attr){
							return input[attr] || '';
						}
					)
				);
				
				jInput.addClass(replaced);
				jInput.after(jButton);
				
				if(simulate){
					var eventType = REG_SUBMIT.test(input.type)? Event.SUBMIT : Event.RESET;
					jButton.bind(Event.CLICK, function(e){
						e.preventDefault();
						if($(form).triggerHandler(eventType, {explicitTarget: input}) !== false){
							jInput.trigger(Event.CLICK);
						}
					});
				}
			});
		},

		/**
		 * Posts a form, optionally to the given url. A handler function may be specified in 
		 * which case the post will be performed via ajax. Otherwise, the form will be posted 
		 * normally. A "submit" or "ajaxsubmit" is fired to the dispatcher automatically.
		 * If an ajax post is performed, this function uses the jQuery.post method.
		 * 
		 * @param {Node} form The form element
		 * @param {String} url The optional url to post to, defaults to the form's action.
		 * @param {Function} handler Optional response handler for ajax posts. See jQuery.post for details.
		 * @param {String} type The type of data to be returned. See jQuery.post for details.
		 */
		post:function(form, url, handler, type) {
			if(!handler) {
				if(Dispatcher.fire(Event.SUBMIT, form)){
					if(url) {
						form.setAttribute('action', url);
					}
					form.submit();
				}
			} else {
				if(Dispatcher.fire(Event.AJAX_SUBMIT, form)){
					var post = Forms.serialize(form);
					var action = url || form.getAttribute('action');
					
					$.post(post, action, handler, type);
				}
			}
		}
	});

	/**
	 * Serializes a given form's input, and returns the post string.
	 *
	 * @static
	 * @method serialize
	 * @param {Node} form The form to serialize
	 * @return {String} The serialized input
	 */
	Forms.serialize = function(form){
		var post = [];
		var input = /(text|select|hidden|pass)/i;
		var select = /select/i; 
		var elements = form.elements || $('input,select,textarea', form);

		for(var i=0; i<elements.length; i++){
			var element = elements[i];
			var type = element.type;
			if(input.test(type) || element.checked){
				if(select.test(element.nodeName)){
					var index = element.selectedIndex;
					if(index >= 0 && element[index]){
						var opt = element[index];
						post.push(element.name, '=', encodeURIComponent(opt.value || opt.text), '&');
					}
				} else {
					post.push(element.name, '=', encodeURIComponent(element.value), '&');
				}
			}
		}

		return post.join('');
	};

	/**
	 * Default settings for the LBi.Forms class.
	 *
	 * @static
	 * @class LBi.Forms.Defaults
	 */
	Forms.Defaults = {
		/**
		 * Defines whether submit events will be routed to the dispatcher.
		 * @property captureSubmit
		 * @type Boolean
		 * @default true
		 */
		captureSubmit: true,
		
		/**
		 * Defines whether change events on inputs are routed to the dispatcher. 
		 * @property captureChange
		 * @type Boolean
		 * @default false
		 */
		captureChange: false,
		
		/**
		 * Defines whether instances will subscribe to the DOMNodeInserted event to parse 
		 * new html automatically.
		 * @property ajaxEnabled
		 * @type Boolean
		 * @default true
		 */
		ajaxEnabled: true,

		/**
		 * Defines the nodes interpreted as "form". Typically this is an actual form, but in specific
		 * cases you might want to target other nodes, or use multiple LBi.Forms instances that target 
		 * differently classed forms, each with individual settings.
		 * @property formSelector
		 * @type String
		 * @default "form"
		 */
		formSelector: 'form',
		
		/**
		 * Defines whether inputs are hinted, using their title attribute as an alternative label. 
		 * @property hintValues
		 * @type Boolean
		 * @default true
		 */
		hintValues: true,
		
		/**
		 * The selector for hinted inputs. Regardless of its value will only target text(area) inputs.
		 * @property hintSelector
		 * @type String
		 * @default "input[title]"
		 */
		hintSelector: 'input[title]',
		
		/**
		 * The class that is set on hinted inputs, when inactive (hinted).
		 * @property hintClass
		 * @type String
		 * @default "blurred"
		 */
		hintClass: 'blurred',
		
		/**
		 * Defines whether input buttons are replaced by styled links.
		 * @property replaceInputs
		 * @type Boolean
		 * @default true
		 */
		replaceInputs: true,

		/**
		 * The selector for replaced buttons. Regardless of its value will only target submit and reset inputs.
		 * @property buttonSelector
		 * @type String
		 * @default "input"
		 */
		buttonSelector: 'input',

		/**
		 * The button template. Copies the classname and value of the input. Additional 
		 * attributes may be copied by marking them with a $.
		 * @property buttonTemplate
		 * @type String
		 * @default "&lt;a href="#" class="$className"&gt;&lt;span&gt;$value&lt;/span&gt;&lt;/a&gt;"
		 */
		buttonTemplate: '<a href="#" class="$className"><span>$value</span></a>',
		
		/**
		 * Class for replaced inputs, may be used to hide them, or move them out of view.
		 * @property replacedClass
		 * @type String
		 * @default "replaced"
		 */
		replacedClass: 'replaced',
		
		/**
		 * Defines whether a click is simulated on the original input to submit or reset a form. 
		 * Ensures that a possible name/value pair is added to the post, similar to a native click.
		 * @property simulateClick
		 * @type Boolean
		 * @default true
		 */
		simulateClick: true
	};
	
	return Forms;

})(jQuery);