(function( $ ) {
  $.fn.extend({
	  validate: function( validator, options, callback ) {
	    var valid = {
	      email: function( item, options ) {
	        return /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i.test( item.val() );
	      },
	      
	      // If options === true then allow an empty URL
	      url: function( item, options ) {
	        var match = /^(https?:\/\/)?(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test( item.val() );
	        if ( match || ( options === true && item.val() == '' ) )
	          return true;
	        return false;
	      },
	      
	      required: function( item, options ) {
	        if ( $( item ).is( ':checkbox' ) )
	          return $( item ).is( ':checked' ) === false ? false : true;
	        else if ( item.val().replace( /^\s+|\s+$/g, "" ).length > 0 )
	          return true;
	        return false;
	      },
	      
	      string: function( item, options ) {
	        if ( item.val().replace( /^\s+|\s+$/g, "" ).length > parseInt( options ) )
	          return true;
	        return false;
	      },
	      
	      equal: function( item, options ) {
	        if ( $( options ).val().replace( /^\s+|\s+$/g, "" ).length == 0 || item.val() == $( options ).val() )
	          return true;
	        return false;
	      }
	    };
	    var in_focus = false;
	    var in_error = function( validator ) {
	      if ( valid[validator.name]( validator.target, validator.options ) === false ) {
	        // Register blur event so we can auto-remove the error
	        validator.target.unbind( 'blur' ).blur( function() {
	          in_error( validator );
	        });
	
	        // Focus on first item
	        if ( in_focus === false ) {
	          validator.target.focus();
	          in_focus = true;
	        }
	
	        // Show error (once)
	        if ( validator.target.data( 'inerror' ) === undefined || validator.target.data( 'inerror' ) === false || validator.target.data( 'inerror' ) === null ) {
	          validator.target.data( 'inerror', true );
	          
	          $( '#error-' + validator.target.attr( 'name' ) ).slideDown( {
	              easing: 'easeOutQuart',
	              complete: function() {
	                // Call the callback function
	                if ( $.isFunction( validator.callback ) )
	                  validator.callback( validator.target, true );
	              }
	          });
	        }
	
	        return 1;
	      }
	
	      $( '#error-' + validator.target.attr( 'name' ) ).slideUp( {
	          easing: 'easeOutQuart',
	          complete: function() {
	            if ( validator.target.data( 'inerror' ) === true && $.isFunction( validator.callback ) )
	              validator.callback( validator.target, false );
	            
	            validator.target.data( 'inerror', false );
	            validator.target.unbind( 'blur' );
	          }
	      });
	      
	      return 0;
	    };
	    
	    var validate_form = function( validators ) {
	      var failed = 0;
	
	      in_focus = false;
	
	      $.each( validators, function( index, validator ) {
	        failed += in_error( validator );
	      });
	
	      if ( failed > 0 )
	        return false;
	      return true;
	    };
	    
	    return this.each( function() {
	      var form = $( this ).parents( 'form' );
	      var validators = form.data( 'validators' );
	
	      if ( validators === undefined || validators === null )
	        validators = [];
	
	      validators[validators.length] = { name: validator, target: $( this ), options: options, callback: callback };
	      form.data( 'validators', validators );
	
	      form.submit( function( event ) {
	        if ( validate_form( $( this ).data( 'validators' ) ) )
	          return true;
	          
	        event.stopImmediatePropagation();
	        return false;
	      });
	      
	      form.data( 'validate', true );
	    } );
	  }
  });
})( jQuery );

