define([
  './core',
  './manipulation/var/rcheckableType',
  './core/init',
  './traversing', // filter
  './attributes/prop',
], function(jQuery, rcheckableType) {
  var r20 = /%20/g,
    rbracket = /\[\]$/,
    rCRLF = /\r?\n/g,
    rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i,
    rsubmittable = /^(?:input|select|textarea|keygen)/i;

  function buildParams(prefix, obj, traditional, add) {
    var name;

    if (jQuery.isArray(obj)) {
      // Serialize array item.
      jQuery.each(obj, function(i, v) {
        if (traditional || rbracket.test(prefix)) {
          // Treat each array item as a scalar.
          add(prefix, v);
        } else {
          // Item is non-scalar (array or object), encode its numeric index.
          buildParams(
            prefix + '[' + (typeof v === 'object' ? i : '') + ']',
            v,
            traditional,
            add
          );
        }
      });
    } else if (!traditional && jQuery.type(obj) === 'object') {
      // Serialize object item.
      for (name in obj) {
        buildParams(prefix + '[' + name + ']', obj[name], traditional, add);
      }
    } else {
      // Serialize scalar item.
      add(prefix, obj);
    }
  }

  // Serialize an array of form elements or a set of
  // key/values into a query string
  jQuery.param = function(a, traditional) {
    var prefix,
      s = [],
      add = function(key, value) {
        // If value is a function, invoke it and return its value
        value = jQuery.isFunction(value) ? value() : value == null ? '' : value;
        s[s.length] = encodeURIComponent(key) + '=' + encodeURIComponent(value);
      };

    // Set traditional to true for jQuery <= 1.3.2 behavior.
    if (traditional === undefined) {
      traditional = jQuery.ajaxSettings && jQuery.ajaxSettings.traditional;
    }

    // If an array was passed in, assume that it is an array of form elements.
    if (jQuery.isArray(a) || (a.jquery && !jQuery.isPlainObject(a))) {
      // Serialize the form elements
      jQuery.each(a, function() {
        add(this.name, this.value);
      });
    } else {
      // If traditional, encode the "old" way (the way 1.3.2 or older
      // did it), otherwise encode params recursively.
      for (prefix in a) {
        buildParams(prefix, a[prefix], traditional, add);
      }
    }

    // Return the resulting serialization
    return s.join('&').replace(r20, '+');
  };

  jQuery.fn.extend({
    serialize: function() {
      return jQuery.param(this.serializeArray());
    },
    serializeArray: function() {
      return this.map(function() {
        // Can add propHook for "elements" to filter or add form elements
        var elements = jQuery.prop(this, 'elements');
        return elements ? jQuery.makeArray(elements) : this;
      })
        .filter(function() {
          var type = this.type;

          // Use .is( ":disabled" ) so that fieldset[disabled] works
          return (
            this.name &&
            !jQuery(this).is(':disabled') &&
            rsubmittable.test(this.nodeName) &&
            !rsubmitterTypes.test(type) &&
            (this.checked || !rcheckableType.test(type))
          );
        })
        .map(function(i, elem) {
          var val = jQuery(this).val();

          return val == null
            ? null
            : jQuery.isArray(val)
              ? jQuery.map(val, function(val) {
                  return { name: elem.name, value: val.replace(rCRLF, '\r\n') };
                })
              : { name: elem.name, value: val.replace(rCRLF, '\r\n') };
        })
        .get();
    },
  });

  return jQuery;
});
