/* http://keith-wood.name/realPerson.html
   Real Person Form Submission for jQuery v1.0.1.
   Written by Keith Wood (kwood{at}iinet.com.au) June 2009.
   Dual licensed under the GPL (http://dev.jquery.com/browser/trunk/jquery/GPL-LICENSE.txt) and 
   MIT (http://dev.jquery.com/browser/trunk/jquery/MIT-LICENSE.txt) licenses. 
   Please attribute the author if you use it. */

(function($) { // Hide scope, no $ conflict

var PROP_NAME = 'realPerson';

/* Real person manager. */
function RealPerson() {
    this._defaults = {
        length: 6, // Number of characters to use
        includeNumbers: false, // True to use numbers as well as letters
        regenerate: 'Click to change', // Instruction text to regenerate
        hashName: '{n}Hash' // Name of the hash value field to compare with,
            // use {n} to substitute with the original field name
    };
}

var CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
var DOTS = [
    ['   *   ', '  * *  ', '  * *  ', ' *   * ', ' ***** ', '*     *', '*     *'],
    ['****** ', '*     *', '*     *', '****** ', '*     *', '*     *', '****** '],
    [' ***** ', '*     *', '*      ', '*      ', '*      ', '*     *', ' ***** '],
    ['****** ', '*     *', '*     *', '*     *', '*     *', '*     *', '****** '],
    ['*******', '*      ', '*      ', '****   ', '*      ', '*      ', '*******'],
    ['*******', '*      ', '*      ', '****   ', '*      ', '*      ', '*      '],
    [' ***** ', '*     *', '*      ', '*      ', '*   ***', '*     *', ' ***** '],
    ['*     *', '*     *', '*     *', '*******', '*     *', '*     *', '*     *'],
    ['*******', '   *   ', '   *   ', '   *   ', '   *   ', '   *   ', '*******'],
    ['      *', '      *', '      *', '      *', '      *', '*     *', ' ***** '],
    ['*     *', '*   ** ', '* **   ', '**     ', '* **   ', '*   ** ', '*     *'],
    ['*      ', '*      ', '*      ', '*      ', '*      ', '*      ', '*******'],
    ['*     *', '**   **', '* * * *', '*  *  *', '*     *', '*     *', '*     *'],
    ['*     *', '**    *', '* *   *', '*  *  *', '*   * *', '*    **', '*     *'],
    [' ***** ', '*     *', '*     *', '*     *', '*     *', '*     *', ' ***** '],
    ['****** ', '*     *', '*     *', '****** ', '*      ', '*      ', '*      '],
    [' ***** ', '*     *', '*     *', '*     *', '*   * *', '*    * ', ' **** *'],
    ['****** ', '*     *', '*     *', '****** ', '*   *  ', '*    * ', '*     *'],
    [' ***** ', '*     *', '*      ', ' ***** ', '      *', '*     *', ' ***** '],
    ['*******', '   *   ', '   *   ', '   *   ', '   *   ', '   *   ', '   *   '],
    ['*     *', '*     *', '*     *', '*     *', '*     *', '*     *', ' ***** '],
    ['*     *', '*     *', ' *   * ', ' *   * ', '  * *  ', '  * *  ', '   *   '],
    ['*     *', '*     *', '*     *', '*  *  *', '* * * *', '**   **', '*     *'],
    ['*     *', ' *   * ', '  * *  ', '   *   ', '  * *  ', ' *   * ', '*     *'],
    ['*     *', ' *   * ', '  * *  ', '   *   ', '   *   ', '   *   ', '   *   '],
    ['*******', '     * ', '    *  ', '   *   ', '  *    ', ' *     ', '*******'],
    ['  ***  ', ' *   * ', '*     *', '*     *', '*     *', ' *   * ', '  ***  '],
    ['   *   ', '  **   ', ' * *   ', '   *   ', '   *   ', '   *   ', '*******'],
    [' ***** ', '*     *', '      *', '     * ', '   **  ', ' **    ', '*******'],
    [' ***** ', '*     *', '      *', '    ** ', '      *', '*     *', ' ***** '],
    ['    *  ', '   **  ', '  * *  ', ' *  *  ', '*******', '    *  ', '    *  '],
    ['*******', '*      ', '****** ', '      *', '      *', '*     *', ' ***** '],
    ['  **** ', ' *     ', '*      ', '****** ', '*     *', '*     *', ' ***** '],
    ['*******', '     * ', '    *  ', '   *   ', '  *    ', ' *     ', '*      '],
    [' ***** ', '*     *', '*     *', ' ***** ', '*     *', '*     *', ' ***** '],
    [' ***** ', '*     *', '*     *', ' ******', '      *', '     * ', ' ****  ']];

$.extend(RealPerson.prototype, {
    /* Class name added to elements to indicate already configured with real person. */
    markerClassName: 'hasRealPerson',

    /* Override the default settings for all real person instances.
       @param  settings  (object) the new settings to use as defaults
       @return  (RealPerson) this object */
    setDefaults: function(settings) {
        $.extend(this._defaults, settings || {});
        return this;
    },

    /* Attach the real person functionality to an input field.
       @param  target    (element) the control to affect
       @param  settings  (object) the custom options for this instance */
    _attachRealPerson: function(target, settings) {
        target = $(target);
        if (target.hasClass(this.markerClassName)) {
            return;
        }
        target.addClass(this.markerClassName);
        var inst = {settings: $.extend({}, this._defaults)};
        $.data(target[0], PROP_NAME, inst);
        this._changeRealPerson(target, settings);
    },

    /* Reconfigure the settings for a real person control.
       @param  target    (element) the control to affect
       @param  settings  (object) the new options for this instance or
                         (string) an individual property name
       @param  value     (any) the individual property value (omit if settings is an object) */
    _changeRealPerson: function(target, settings, value) {
        target = $(target);
        if (!target.hasClass(this.markerClassName)) {
            return;
        }
        settings = settings || {};
        if (typeof settings == 'string') {
            var name = settings;
            settings = {};
            settings[name] = value;
        }
        var inst = $.data(target[0], PROP_NAME);
        $.extend(inst.settings, settings);
        target.prevAll('.realperson-challenge,.realperson-hash').remove().end().
            before(this._generateHTML(target, inst));
    },

    /* Generate the additional content for this control.
       @param  target  (jQuery) the input field
       @param  inst    (object) the current instance settings
       @return  (string) the additional content */
    _generateHTML: function(target, inst) {
        var text = '';
        for (var i = 0; i < inst.settings.length; i++) {
            text += CHARS.charAt(Math.floor(Math.random() *
                (inst.settings.includeNumbers ? 36 : 26)));
        }
        var html = '<div class="realperson-challenge"><div class="realperson-text">';
        for (var i = 0; i < DOTS[0].length; i++) {
            for (var j = 0; j < text.length; j++) {
                html += DOTS[CHARS.indexOf(text.charAt(j))][i].replace(/ /g, '&nbsp;') +
                    '&nbsp;&nbsp;';
            }
            html += '<br>';
        }
        html += '</div><div class="realperson-regen">' + inst.settings.regenerate +
            '</div></div><input type="hidden" class="realperson-hash" name="' +
            inst.settings.hashName.replace(/\{n\}/, target.attr('name')) +
            '" value="' + this._hash(text) + '">';
        return html;
    },

    /* Remove the real person functionality from a control.
       @param  target  (element) the control to affect */
    _destroyRealPerson: function(target) {
        target = $(target);
        if (!target.hasClass(this.markerClassName)) {
            return;
        }
        target.removeClass(this.markerClassName).
            prevAll('.realperson-challenge,.realperson-hash').remove();
        $.removeData(target[0], PROP_NAME);
    },

    /* Compute a hash value for the given text.
       @param  value  (string) the text to hash
       @return  the corresponding hash value */
    _hash: function(value) {
        var hash = 5381;
        for (var i = 0; i < value.length; i++) {
            hash = ((hash << 5) + hash) + value.charCodeAt(i);
        }
        return hash;
    }
});

/* Attach the real person functionality to a jQuery selection.
   @param  command  (string) the command to run (optional, default 'attach')
   @param  options  (object) the new settings to use for these instances (optional)
   @return  (jQuery) for chaining further calls */
$.fn.realperson = function(options) {
    var otherArgs = Array.prototype.slice.call(arguments, 1);
    return this.each(function() {
        if (typeof options == 'string') {
            $.realperson['_' + options + 'RealPerson'].
                apply($.realperson, [this].concat(otherArgs));
        }
        else {
            $.realperson._attachRealPerson(this, options || {});
        }
    });
};

/* Initialise the real person functionality. */
$.realperson = new RealPerson(); // singleton instance

$('.realperson-challenge').live('click', function() {
    $(this).next().next().realperson('change');
});

})(jQuery);


