/**
 * @author Kyle Florence <kyle[dot]florence[at]gmail[dot]com>
 * @website https://github.com/kflorence/jquery-deserialize/
 * @version 1.2.1
 *
 * Dual licensed under the MIT and GPLv2 licenses.
 */
(function( jQuery, undefined ) {

var push = Array.prototype.push,
    rcheck = /^(?:radio|checkbox)$/i,
    rplus = /\+/g,
    rselect = /^(?:option|select-one|select-multiple)$/i,
    rvalue = /^(?:button|color|date|datetime|datetime-local|email|hidden|month|number|password|range|reset|search|submit|tel|text|textarea|time|url|week)$/i;

function getElements( elements ) {
    return elements.map(function() {
            return this.elements ? jQuery.makeArray( this.elements ) : this;
        }).filter( ":input:not(:disabled)" ).get();
}

function getElementsByName( elements ) {
    var current,
        elementsByName = {};

    jQuery.each( elements, function( i, element ) {
        current = elementsByName[ element.name ];
        elementsByName[ element.name ] = current === undefined ? element :
            ( jQuery.isArray( current ) ? current.concat( element ) : [ current, element ] );
    });

    return elementsByName;
}

jQuery.fn.deserialize = function( data, options ) {
    var i, length,
        elements = getElements( this ),
        normalized = [];

    if ( !data || !elements.length ) {
        return this;
    }

    if ( jQuery.isArray( data ) ) {
        normalized = data;

    } else if ( jQuery.isPlainObject( data ) ) {
        var key, value;

        for ( key in data ) {
            jQuery.isArray( value = data[ key ] ) ?
                push.apply( normalized, jQuery.map( value, function( v ) {
                    return { name: key, value: v };
                })) : push.call( normalized, { name: key, value: value } );
        }

    } else if ( typeof data === "string" ) {
        var parts;

        data = data.split( "&" );

        for ( i = 0, length = data.length; i < length; i++ ) {
            parts =  data[ i ].split( "=" );
            push.call( normalized, {
                name: decodeURIComponent( parts[ 0 ].replace( rplus, "%20" ) ),
                value: decodeURIComponent( parts[ 1 ].replace( rplus, "%20" ) )
            });
        }
    }

    if ( !( length = normalized.length ) ) {
        return this;
    }

    var current, element, j, len, name, property, type, value,
        change = jQuery.noop,
        complete = jQuery.noop,
        names = {};

    options = options || {};
    elements = getElementsByName( elements );

    // Backwards compatible with old arguments: data, callback
    if ( jQuery.isFunction( options ) ) {
        complete = options;

    } else {
        change = jQuery.isFunction( options.change ) ? options.change : change;
        complete = jQuery.isFunction( options.complete ) ? options.complete : complete;
    }

    for ( i = 0; i < length; i++ ) {
        current = normalized[ i ];

        name = current.name;
        value = current.value;

        if ( !( element = elements[ name ] ) ) {
            continue;
        }

        type = ( len = element.length ) ? element[ 0 ] : element;
        type = ( type.type || type.nodeName ).toLowerCase();
        property = null;

        if ( rvalue.test( type ) ) {
            if ( len ) {
                j = names[ name ];
                element = element[ names[ name ] = ( j == undefined ) ? 0 : ++j ];
            }

            change.call( element, ( element.value = value ) );

        } else if ( rcheck.test( type ) ) {
            property = "checked";

        } else if ( rselect.test( type ) ) {
            property = "selected";
        }

        if ( property ) {
            if ( !len ) {
                element = [ element ];
                len = 1;
            }

            for ( j = 0; j < len; j++ ) {
                current = element[ j ];

                if ( current.value == value ) {
                    change.call( current, ( current[ property ] = true ) && value );
                }
            }
        }
    }

    complete.call( this );

    return this;
};

})( jQuery );