var PollDaddy;

(function( $ ) {
  // Global PollDaddy object
  PollDaddy = {
    highlight_colour: '#fdf5e1',
    error_colour:     '#ccaaaa',
    opacity:          0.4,
    loading_icon:     '<img src="/images/ajax-loader.gif" width="16" height="16" alt="loading"/>',
    
    /**
     *  Highlight an item to indicate a positive result or something has changed, saved, or been updated
     *    callback: optional function to call once highlight has finished
     */
    is_fatal: function( response ) {
      // If the response is XML, has an error node and is fatal, then display the error
      if ( typeof response == 'object' && response.documentElement.nodeName == 'error' && ( response.documentElement.getAttribute( 'level' ) == 'fatal' || response.documentElement.getAttribute( 'level' ) == 'upgrade' ) ) {
        var nodes = response.documentElement.childNodes;
        
        for ( var pos = 0; pos < nodes.length; pos++ ) {
          if ( nodes[pos].nodeName == 'html' ) {
            $.fn.colorbox( { html: nodes[pos].childNodes[0].nodeValue, open: true, inline: false, scrolling: false, opacity: PollDaddy.opacity, onComplete: function() {
              $( '#error' ).unbind( 'click' ).click( function() {
                $.fn.colorbox.close();
                return false;
              });
            } } );
            return true;
          }
        }
      }

      return false;
    },
    
    init: function() {
      // Global ajax error handler
      $().bind( 'ajaxError', function( event, request ) {
        var html = '<div class="dialog dialog-error" id="error"><div class="body"><div class="title">Server Error ' + request.status + '</div><div class="text">Ooops - an error occurred on the server</div></div>';
        
        html += '<div class="controls"><div class="right"><a class="cancel button" onclick="jQuery.fn.colorbox.close(); return false;" href="#">Ok</a><p class="clear"/></div></div><p class="clear"/></div></div>';
  
        $.fn.colorbox( { html: html, open: true, href: false, scrolling: false, inline: false, scrolling: false, opacity: PollDaddy.opacity, onComplete: function() {
          $( '#error' ).unbind( 'click' ).click( function() {
            $.fn.colorbox.close();
            return false;
          });
        } } );
      });
      
      // Generic dismiss-and-remove for notice messages
      $( document ).ready(function() {
        // General 'dismiss me' for any information notifications
        $( '.dismiss' ).click( function() {
          $( this ).parent().remove();
          return false;
        });

        // Hover effect
        $( '.hovercontrol' ).live( 'mouseover', function() {
          $( this ).find( '.hover' ).css( 'visibility', 'visible' );
        }).live( 'mouseout', function() {
          $( this ).find( '.hover' ).css( 'visibility', 'hidden' );
        });

        $( '.hovercontrol .hover' ).css( 'visibility', 'hidden' );

        // General JS/basic show
        $( '.javascript' ).show();
        $( '.basic' ).hide();

        // Any print buttons
        $( 'a.print-popup' ).click( function() { window.print(); return false; });
        
        // Preview handlers
        $( 'a.preview' ).colorbox( {width: '780px', height: '500px', iframe:true, opacity: PollDaddy.opacity, onOpen: function() {
          $( '#colorbox' ) .addClass( 'popup-preview' );
        } } );

        // Preload loading icons
        PollDaddy.preload( '/images/ajax-loader.gif', 16, 16 );
        PollDaddy.preload( '/images/dialog-load.gif', 16, 16 );
        PollDaddy.preload( '/images/nav-load.gif', 11, 11 );
      });
    },
    
    /**
     * Add an image to be preloaded
     *
     */
    preload: function( url, width, height ) {
      var img = new Image( width, height );
      img.src = url;
    }
  };
  
  PollDaddy.init();
  
  // Extend jQuery with some goodies
  $.fn.extend({
    /**
     *  Highlight an item to indicate a positive result or something has changed, saved, or been updated
     *    callback: optional function to call once highlight has finished
     */
    highlight: function( callback ) {
      return this.each( function() {
        var save = $( this ).css( 'backgroundColor' );
      
        $( this ).animate( { backgroundColor: PollDaddy.highlight_colour }, 450 ).animate( { backgroundColor: save } ).animate( { backgroundColor: PollDaddy.highlight_colour }, 150 ).animate( { backgroundColor: save }, function() {
          if ( callback && $.isFunction( callback ) )
            callback();
        } );
      });
    },
  
    /**
     *  Highlight an item to indicate a negative result or that something has failed to save
     *    callback: optional function to call once error has finished
     */
    error: function( callback ) {
      return this.each( function() {
        var save = $( this ).css( 'backgroundColor' );
      
        $( this ).animate( { backgroundColor: PollDaddy.error_colour }, 450 ).animate( { backgroundColor: save } ).animate( { backgroundColor: PollDaddy.error_colour }, 150 ).animate( { backgroundColor: save }, function() {
          if ( callback && $.isFunction( callback ) )
            callback();
        } );
      });
    },
  
    /**
     *  Highlight an item and remove it from the DOM. Used to indicate that something has been deleted
     *    callback: optional function to call once removed
     */
    fadeOutRemove: function( callback ) {
      return this.each( function() {
        var item = $( this ).fadeOut( 'medium', function() {
          item.remove();
          
          if ( callback && $.isFunction( callback ) )
            callback();
        });
      });
    },

    /**
     *  Copy keypresses from the item to the target element
     *    target:     target item to copy to
     *    empty_name: what to display if the input item is empty
     *    limit:      maximum number of characters to display at the target. More than this will result in an ellipsis
     */    
    copy_key: function( target, empty_name, limit ) {
      return this.each( function() {
        $( this ).keyup( function() {
          if ( $( this ).val().length > limit )
            $( target ).html( $( this ).val().substr( 0, limit ) + '&hellip;' );
          else if ( $( this ).val().length == 0 )
            $( target ).text( empty_name );
          else
            $( target ).text( $( this ).val() );
        });
      });
    },

    /**
     *  Fancy vertical scroller. Apply this to the scroll bar
     *    stage:  The DIV to scroll when the scroll bar is moved. Overflow will be set
     *    args:   Optional args
     *        - stage_content - The element inside the stage that contains the actual content. Default to first child
     *        - start         - Element on the stage that the scroller should initially scroll to
     */    
    scroller: function( stage, args ) {
      var scroll = $( this );
      var opts = $.extend( {
        stage_content: $( $( stage ).children()[0] ),
        start: false
      }, args);

      return this.each( function() {
        // Set overflow
        $( stage ).css( 'overflow', 'hidden' );

        // Set size of scroller
        $( this ).css( 'width', Math.round( ( $( stage ).width() / opts.stage_content.width() ) * 100.0 ) + '%' );

        var slide = function( animate ) {
          // Turn the left offset of the scrollbar into a %
          var current      = parseInt( scroll.css('left') );
          var scroll_width = scroll.parent().width() - scroll.width();
          var percent      = current / scroll_width;
          var stage_width  = opts.stage_content.width() - scroll.parent().width();
          var offset       = ( stage_width * percent );

          // Set margin of stage content
          if ( animate === true )
            opts.stage_content.animate( { marginLeft: '-' + offset + 'px' }, 'medium', 'easeOutQuart' );
          else
            opts.stage_content.css( 'margin-left', '-' + offset + 'px' );
        };

        // Is there a start offset?
        if ( opts.start !== false && opts.start.length > 0 ) {
          var left      = ( $( opts.start ).position().left + $( opts.start ).width() ) - $( opts.stage_content ).position().left;
          var percent   = left / $( opts.stage_content ).width();
          var scroller  = scroll.parent().width() - scroll.width();

          // Round up to end, or ignore if totally on first page
          if ( percent > 0.95 )
            percent = 1;
          else if ( left < $( stage ).width() )
            percent = 0;
            
          scroll.css( 'left', Math.round( percent * ( scroll.parent().width() - scroll.width() ) ) );
          slide( true );
        }
        
        // Mouse-wheel
        if ( jQuery.isFunction( jQuery.fn.mousewheel ) ) {
          $( stage ).mousewheel( function( event, delta ) {
            var scroll_to = parseInt( scroll.css( 'left' ) ) + parseInt( ( ( 0 - delta ) * 30 ) );
          
            if ( scroll_to > scroll.parent().width() - scroll.width() )
              scroll_to = scroll.parent().width() - scroll.width();
            else if ( scroll_to < 0 )
              scroll_to = 0;
          
            scroll.css( 'left', scroll_to );
            slide( false );
            return false;
          });
        }

        // Click-to-scroll
        $( this ).parent().click( function( event, ui ) {
          var offset = event.clientX - $( this ).offset().left;
          var scroll_to = offset - ( scroll.width() / 2 );
          
          if ( scroll_to > scroll.parent().width() - scroll.width() )
            scroll_to = scroll.parent().width() - scroll.width();
          else if ( scroll_to < 0 )
            scroll_to = 0;
          
          scroll.css( 'left', scroll_to );
          slide( true );
          return false;
        });
        
        // Activate scroller
        $( this ).draggable( {
          axis: 'horizontal',
          containment: $( this ).parent(),
          drag: function( event, ui ) {
            slide();
          }
        });
      });
    },
    
    // Attach 'menu_items' to selector as menu
    menu: function( menu_items, args ) {
      var opts = $.extend( {
        voffset: -1,
        hoffset: 2
      }, args);
      
      return this.each( function() {
        $( this ).unbind( 'click' );
        
        $( this ).click( function() {
          function click_anywhere() {
            $( menu_items ).slideUp( 100, 'easeOutQuart' );
            $( 'body' ).unbind( 'click', click_anywhere );
            return false;
          }
          
          // Click anywhere handler
          $( 'body' ).click( click_anywhere );

          // Click on items handler
          $( menu_items ).find( 'a' ).click( function( event ) {
            event.stopPropagation();
            return true;
          });

          if ( $( menu_items + ':visible' ).length == 0 ) {
            $( menu_items ).css( { top: $( this ).position().top + $( this ).height() + opts.voffset, left: $( this ).position().left + opts.hoffset } );
            $( menu_items ).slideDown( 100, 'easeOutQuart' );
            return false;
          }
          else {
            return click_anywhere();
          }
        });
      });
    },

    /**
     *  Popup dialog. Apply to element that fires the popup.
     *    - box:  Target element to popup (on page). If item is not on page (i.e. load from remote URL) then this can be passed the same as 'args'
     *    - args: Optional args. Pass a 'success' or 'start' function, or an object:
     *          - start: fired when the dialog is started
     *          - confirmed: fired when the dialog 'confirm' is pressed
     */    
    dialog: function( box, args ) {
      // If we're only passed an object or function then our dialog contents are remote, otherwise they are on the page
      if ( typeof box == 'object' || typeof box == 'undefined' || $.isFunction( box ) ) {
        args = box;
        if ( $.isFunction( args ) )
          args = { start: args };

        box = false;
      }
        
      // If arg is a function then it's the end callback
      if ( $.isFunction( args ) )
        args = { success: args };
      
      // Merge in the user-supplied arguments
      var original = false;
      var opts = $.extend( {
        start:     function() {},
        confirmed: false,
        success:   function() {},
        error:     function() {},
        auto_link: true,
        
        target:    '.dialog',
        finished: function() {},

        select_value: -1
      }, args );

      var clicked_remote = function( that ) {
        var resize = function() {
          $.fn.colorbox.resize();
        };
        
        var remote_complete = function() {
    		  if ( opts.auto_link ) {
      			$( '#cboxLoadedContent ' + opts.target + ' a' ).unbind( 'click' ).click( function() {
      			  reload( $( this ).attr( 'href' ) );
      			  return false;
      			});
      		}

    			// Catch close
    			$( '#cboxLoadedContent ' + opts.target + ' .done' ).unbind( 'click' ).click( function() {
    			  $.fn.colorbox.close();
    			  opts.finished( opts.target );
    			  return false;
    			});

    			opts.start( $( '#cboxLoadedContent ' + opts.target ), { resize: resize, reload: reload, refresh: refresh } );
        };
        
        var refresh = function( new_content ) {
          $.fn.colorbox( { html: new_content, open: true, inline: false, scrolling: false, opacity: PollDaddy.opacity, onComplete: remote_complete } );
        };
        
        var reload = function( url ) {
          $.fn.colorbox( {
            inline: false,
            open: true,
            opacity: PollDaddy.opacity,
            scrolling: false,
            close: '',
            href: url,
            html: false,
            onComplete: remote_complete
          });
      	};
    		
    		original = that;
        
    		// Fire off the first load
        reload( $( that ).attr( 'href' ) );
        return false;
      };
      
      var clicked_local = function( that ) {
        original = that;
        
        // XXX isnt this too soon? box isnt even open
        opts.start( $( that ), $( box ) );
      
        $( box ).find( 'a.confirm' ).unbind( 'click' ).click( function() {
          $.fn.colorbox.close();

          if ( opts.confirmed === false ) {
            $( original ).unbind( 'click' );
            $( original ).click();
          }
          else
            opts.confirmed( original );

          return false;
        });

        $( box ).find( 'a.cancel' ).unbind( 'click' ).click( function() {
          $.fn.colorbox.close();
          return false;
        });

        $( box ).find( 'form' ).ajaxForm( {
          beforeSubmit: function() {
            $( box ).find( '.loader' ).show();
            $( box ).find( '.editor-buttons' ).hide();
          },
          success: function( response ) {
            $( box ).find( '.loader' ).hide();
            $( box ).find( '.editor-buttons' ).show();

            // Call the user's callback
            if ( PollDaddy.is_fatal( response ) )
              opts.error( response, original );
            else {
              opts.success( response, original );

            // Close the box
            $.fn.colorbox.close();
          }
        }} );
        
        $.fn.colorbox( {
          inline: true,
          open: true,
          opacity: PollDaddy.opacity,
          scrolling: false,
          close: '',
          href: $( box ),
          html: false,
          speed: 0,
          onComplete: function() {}
        } );
        return false;
      };
            
      var clicker = clicked_local;
      
      if ( box === false )
        clicker = clicked_remote;
        
      return this.each( function() {
        // Do a bit of DOM sniffing - what type of node are we attached to?
        if ( this.type == 'select-one' ) {
          // Select - only appear when a specific element is selected
          $( this ).unbind( 'change' ).change( function() {
            if ( $( this ).val() == opts.select_value )
              return clicker( this );
          });
        }
        else {
          // Anything else that can be clicked - open colorbox
         $( this ).unbind( 'click' ).click( function() { return clicker( this ) } );
        }
      });
    },
    
    /**
     * Automatic select of all text when the focus is received
     */
    auto_select: function() {
      return this.each( function() {
        $( this ).focus( function() {
          this.select();
          return true;
        })
      });
    },
    
    /**
     * Generate a random password when the item is clicked. Password is stored in the target
     */
    auto_password: function( target ) {
      return this.each( function() {
        $( this ).unbind( 'click' ).click( function() {
          var password = '';
          var length = ( Math.random() * 8 ) + 8;
          var source = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_';
          
          for ( var x = 0; x < length; x++ ) {
            password += source.substr( Math.random() * source.length, 1 );
          }
          
          $( target ).val( password );
          return false;
        })
      });
    },
    
    focus_text: function( text ) {
      return this.each( function() {
        var item = this;
        
        $( this ).parents( 'form' ).submit( function() {
          if ( $( item ).val() == text || $( item ).hasClass( 'idle' ) )
            $( item ).val( '' );
        });
        
        if ( $( this ).val() == '' || $( this ).val() == text ) {
          $( this ).addClass( 'idle' );
          $( this ).val( text );
        }
        
        $( this ).unbind( 'focus' ).focus( function() {
           if ( $( item ).val() == '' )
             $( item ).addClass( 'idle' );

           if ( $( item ).val() == text || $( item ).hasClass( 'idle' ) )
            $( item ).val( '' );

           $( item ).removeClass( 'idle' );
         });
         
         $( this ).unbind( 'blur' ).blur( function() {
             if ( $( item ).val() == '' ) {
               $( item ).addClass( 'idle' );
               $( item ).val( text );
             }
             else {
              $( item ).removeClass( 'idle' );
             }
         });
      });
    },
    
    loading: function( args ) {
      var opts = $.extend( {
        hide: false
      }, args);
      
      return this.each( function() {
        if ( this.nodeName == 'A' ) {
          if ( $( this ).data( 'before' ) !== undefined && $( this ).data( 'before' ) !== false && $( this ).data( 'before' ) !== null ) {
            $( this ).replaceWith( $( this ).data( 'before' ) );
            $( this ).data( 'before_class', false );
            $( this ).data( 'before_click', false );
          }
          else {
            // Add special link-load class, removing all other link-XXXX classes
            $( this ).data( 'before', $( this ).clone( true ) );
            $( this ).attr( 'class', $( this ).attr( 'class' ).replace( /link-\w*/g, '' ) );
            $( this ).addClass( 'link-load' );
            $( this ).unbind( 'click' );
            $( this ).click( function() { return false; });
          }
        }
        else {
          if ( $( this ).data( 'before' ) !== undefined && $( this ).data( 'before' ) !== false && $( this ).data( 'before' ) !== null ) {
            // Hide loading icon, show original
            $( this ).html( $( this ).data( 'before' ) );
            $( this ).data( 'before', false );
          }
          else {
            // Show loading icon
            $( this ).data( 'before', $( this ).html() );
            $( this ).html( PollDaddy.loading_icon );
          }
        }
      });
    },
    
    disable_when_empty: function( button, others ) {
      return this.each( function() {
        var list = [this];
        
        if ( others ) {
          for ( var x = 0; x < others.length; x++ )
            list[list.length] = others[x];
        }
        
        var disable = function( item ) {
          var notempty = 0;
          
          for ( var x = 0; x < list.length; x++ ) {
            if ( $( list[x] ).val() != '' )
              notempty++;
          }
            
  				if ( notempty == list.length )
			      $( button ).removeAttr( 'disabled' ).css( 'opacity', 1 );
  				else
				    $( button ).attr( 'disabled', 'disabled' ).css( 'opacity', 0.5 );
        };
        
        // Initial setting
        disable();
        
        // Monitor keypresses
        for ( var x = 0; x < list.length; x++ ) {
          $( list[x] ).keyup( function() { disable(); } );
        }
      });
    },
    
    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 ).attr( '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 );

