(function(){
	
    if(!jQuery) {
        return;
    }
    
    var $ = jQuery;
   
    /**@plugin ajaxFormValidator(settings)
     * 
     * @usage (see documentation below for more setting options)
     *     
     *     $('form').ajaxFormValidator({
     *         validateService: 'URL TO VALIDATION SERVICE'
     *     });
     * 
     * @param {Object} settings
     */
    
    $.fn.ajaxFormValidator = function(settings) {
        
        /* Default settings, overridden by settings parameter.
         */
        settings = jQuery.extend({
            /**@setting prefix OPTIONAL
             *     Prefix for validation bindings (through field className)
             *     ie. 'js-validate-asString' on which 'js-validate-' is
             *     the prefix.
             *     
             * @setting triggerClassName OPTIONAL
             *     Class name of the trigger container, the trigger listener
             *     will be attached to elements containing this className.
             *     
             * @setting errorClassName OPTIONAL
             *     This classname will be added to the trigger (group) element
             *     when a validation error is received.
             * 
             * @setting validateService REQUIRED!!!
             *     URI to the validation service, should NOT end with a /
             */
            prefix: "validate-",
            triggerClassName: "field-group",
            errorClassName: "error",
			validateService: ""
        },settings);
        
        /* Iterate over found forms and attach sandboxed validator
         * functionality to each.
         */
        this.each(function(){
        	/* Due to <form> dependencies, we make it mandatory
             * to apply ajaxFormValidator on form nodes only.
             */ 
            if(!/^form$/i.test(this.tagName)) {
                throw "ajaxFormValidator can only be attached to forms!";
            }
            if(!/form-container/.test(this.className)){
            	return;
            }
            /* Variables we use for validation triggering.
             */
            var last = null, current;
            
            /**@class Validator
             *     Each time a validation event gets triggered, we create
             *     a Validator for each field group. The validator
             *     collects the fields to be validated and starts
             *     an ajax query process. Once this process is completed
             *     it fires the 'onDone' callback. If no reference is saved
             *     to the Validator instance, the garbage collector will
             *     dispose the instance. 
             * 
             * @property {DOMElement} groupElement
             *     See constructor param.
             * 
             * @property {Array} targets
             *     Collection of form fields that have validation bindings
             *     set in their 'className' attributes. Fields are collected
             *     on instantiation of the Validator class.
             *     
             * @property {Array} messages
             *     Collection of validation results, empty on instantiation,
             *     will be filled as results are received from the validation
             *     service.
             *     
             * @property {Boolean} valid
             *     True by default, will be set to false if the validation
             *     service returns at least 1 form error at validation time.
             * 
             * @param {DOMElement} group 
             *     Container of formfields, the validation trigger listener
             *     gets attached to this element.
             */
            var Validator = function(group) {
                this.groupElement = group;
                this.targets = $.makeArray($(":input[class*="+settings.prefix+"]", group));
                this.messages = [];
                this.valid = true;
            }
            
            /* Validator Class methods
             */
            Validator.prototype = {
                
                /**@method onDone()
                 *     This is an empty placeholder, it should be replaced after
                 *     creating a Validator instance with a method that
                 *     handles the 'onDone' event.
                 *     If no method replaces the default method, nothing happens.
                 */
                onDone: function(){},
                
                /**@method extractValidationMethod(node)
                 *     Extracts the validation service addendum path from
                 *     the className. It differentiates between the 'required'
                 *     binding and other validation bindings.
                 * 
                 * @param {DOMElement} node
                 *     Form field containing validation bindings through it's
                 *     className attribute.
                 * 
                 * @return {String}
                 *     Returns a locator URI for the validation service.
                 */
                extractValidationMethod: function(node){
                    var candidates = node.className.split(' '),
                    	i = candidates.length,
                        method,
                        result = [node.id];
                    
	                while(i--){
	                    if(!/validate-/.test(candidates[i])){
	                        continue;
	                    }
	                    
	                    method = candidates[i].split('-')[1];
	                    
	                    if(/^required$/i.test(method) ){
	                    	result.push(method)
	                    } else {
	                    	result.unshift(method)
	                    }
	                }

                    if(result.length <= 1) {
                    	return false;
                    }
                    
                    return result.join('/');
                },
                
                /**@method process()
                 *     Asynchronously iterates over 'targets' and initiates
                 *     a validation service request for each target.
                 *     Once the process is done, the 'onDone' callback will
                 *     be invoked and the 'renderValid' or 'renderInvalid' methods
                 *     will be called depending on validatorInstance.valid value.
                 * 
                 */
                process: function(){
                    if (this.targets.length) {
                        var target = this.targets.shift();
                        var method = this.extractValidationMethod(target);

                        if(method) {
                        	var data = $(target).serialize();
                        	this.send(data, method, this);
                        } else {
                        	this.process();
                        }
                        target = null;
                        return;
                    }

                    if(this.valid) {
                        this.renderValid();
                    } else {
                        this.renderInvalid();
                    }
                    this.onDone();
                },
                
                /**@method renderValid()
                 *     Removes className 'error' to validatorInstance.groupElement
                 *     and sets the text to an empty string in any DOMElement child
                 *     of groupElement containing className 'groupError'.
                 */
                renderValid : function(){
                    $(this.groupElement).removeClass('error');
                    $('.group-error', this.groupElement).text('');
                },
                
                /**@method renderInalid()
                 *     adds className 'error' to validatorInstance.groupElement
                 *     and appends the messages to any DOMElement child
                 *     of groupElement containing className 'groupError'.
                 */
                renderInvalid : function() {
                    $(this.groupElement).addClass(settings.errorClassName);
                    $('.group-error', this.groupElement).text('');
                    var msg;
                    while(msg = this.messages.shift()) {
                    	if(msg.message) {
                    		$('.group-error', this.groupElement).html($('.group-error', this.groupElement).html()+"<div class='rule'>"+msg.message+"</div>");
                    	}
                    }
                },
                
                /**@method send(data, method, validator)
                 *     Sends a validation request to validation service.
                 *     Passes validation method and required flag by 
                 *     appending parts to the validation service URI.
                 *     Adds the response data to validatorInstance.messages and calls
                 *     validatorInstance.process method when a response
                 *     is successfully received.
                 *     Currently does nothing on response error.
                 * 
                 * @param {Object} data
                 *     JSON data containing 'valid' and 'message' properties.
                 *     
                 * @param {String} method
                 *     Validation method and required flag URI addition.
                 *     
                 * @param {Object} validator
                 *     Validator instance.
                 */
                send : function(data, method, validator){
                    if(!validator) {
                        validator = this;
                    }
                    $.ajax({
                        url: settings.validateService + "/" + method,
    					method: "POST",
    					data: data,
    					dataType: "json",
                        success: function(json) {
                            if(!json.valid) {
                                validator.valid = false;
                            }
                            validator.messages.push(json);
                            validator.process();
    					},
    					error: function(){}
                    });
                }
            };

            
            /**@method trigger(e)
             *     The trigger method gets attached to an element containing
             *     form fields (group element). Whenever the event 'trigger'
             *     is listening to occurs, it checks to see if a validation
             *     action needs to be performed. Currently designed for 
             *     'focus' events.
             *     If a validation action should be performed, it creates a
             *     Validator instance and invokes it's 'process' method.
             * 
             * @param {Object} e
             *     DOM Event.
             */
            var trigger = function(e) {
                current = $(e.target).closest("."+settings.triggerClassName);
                current = current.length ? current[0] : null;
                if(last && current !== last) {
                    new Validator(last).process();
                }
                if(current !== last) {
                    last = current;
                }
            }
            
            /* Attach trigger method as 'focus' listener to all form 
             * fields within the container element (group element).
             */
            $(":input", this).focus(trigger);
            
            /* Submitstate
             * 0 - idle (stops submit)
             * 1 - validating (stops submit)
             * 2 - valid form (allow submit)
             */
            var submitState = 0;
            
            /* Form reference for possible submision and listener attaching.
             */
            var form = this;
            
            /**@method resolveSubmit
             *     Intercepts form submit action and initiates validation
             *     processes. Allows submit when form validates correctly.
             *     
             * @param {DOMEvent} e
             *     
             */
            var resolveSubmit = function(e) {

                if (submitState === 0 || submitState === 1) {
                    e.preventDefault();
                }
                if (submitState === 1 || submitState === 2) {
                    return submitState === 1 ? false : true;
                }
                
                submitState = 1;
                
                var activeValidators = [];
                
                var hasErrors = false;
                
                var resolveValidator = function(){
                	
                    var i = activeValidators.length;
                    while(i--) {
                        if(activeValidators[i] === this) {
                            hasErrors = hasErrors ? hasErrors : !activeValidators[i].valid;
                            activeValidators.splice(i, 1);
                        }
                    }

                    if(!activeValidators.length && !hasErrors) {
                    	$('button[type=submit]', form).attr('disabled', 'disabled');
                    	submitState = 2;
                    	form.submit();
                    } else if (!activeValidators.length) {
                        submitState = 0;
                    }
                }
                
                $("."+settings.triggerClassName, this).each(function(){
                    var validator = new Validator(this);
                    validator.onDone = resolveValidator;
                    activeValidators.push(validator);
                    validator.process();
                });
            }
            
            /* Attach method resolveSubit to form submit event.
             */
            $(form).submit(resolveSubmit);
        });
    }
    
    
    
})();

