define([
  '../core',
  '../var/indexOf',
  './var/rneedsContext',
  '../selector',
], function(jQuery, indexOf, rneedsContext) {
  var risSimple = /^.[^:#\[\.,]*$/;

  // Implement the identical functionality for filter and not
  function winnow(elements, qualifier, not) {
    if (jQuery.isFunction(qualifier)) {
      return jQuery.grep(elements, function(elem, i) {
        /* jshint -W018 */
        return !!qualifier.call(elem, i, elem) !== not;
      });
    }

    if (qualifier.nodeType) {
      return jQuery.grep(elements, function(elem) {
        return (elem === qualifier) !== not;
      });
    }

    if (typeof qualifier === 'string') {
      if (risSimple.test(qualifier)) {
        return jQuery.filter(qualifier, elements, not);
      }

      qualifier = jQuery.filter(qualifier, elements);
    }

    return jQuery.grep(elements, function(elem) {
      return indexOf.call(qualifier, elem) >= 0 !== not;
    });
  }

  jQuery.filter = function(expr, elems, not) {
    var elem = elems[0];

    if (not) {
      expr = ':not(' + expr + ')';
    }

    return elems.length === 1 && elem.nodeType === 1
      ? jQuery.find.matchesSelector(elem, expr) ? [elem] : []
      : jQuery.find.matches(
          expr,
          jQuery.grep(elems, function(elem) {
            return elem.nodeType === 1;
          })
        );
  };

  jQuery.fn.extend({
    find: function(selector) {
      var i,
        len = this.length,
        ret = [],
        self = this;

      if (typeof selector !== 'string') {
        return this.pushStack(
          jQuery(selector).filter(function() {
            for (i = 0; i < len; i++) {
              if (jQuery.contains(self[i], this)) {
                return true;
              }
            }
          })
        );
      }

      for (i = 0; i < len; i++) {
        jQuery.find(selector, self[i], ret);
      }

      // Needed because $( selector, context ) becomes $( context ).find( selector )
      ret = this.pushStack(len > 1 ? jQuery.unique(ret) : ret);
      ret.selector = this.selector ? this.selector + ' ' + selector : selector;
      return ret;
    },
    filter: function(selector) {
      return this.pushStack(winnow(this, selector || [], false));
    },
    not: function(selector) {
      return this.pushStack(winnow(this, selector || [], true));
    },
    is: function(selector) {
      return !!winnow(
        this,

        // If this is a positional/relative selector, check membership in the returned set
        // so $("p:first").is("p:last") won't return true for a doc with two "p".
        typeof selector === 'string' && rneedsContext.test(selector)
          ? jQuery(selector)
          : selector || [],
        false
      ).length;
    },
  });
});
