text: 'use strict';
  
  /**
   * @ngdoc filter
   * @name filter
   * @kind function
   *
   * @description
   * Selects a subset of items from `array` and returns it as a new array.
   *
   * @param {Array} array The source array.
   * @param {string|Object|function()} expression The predicate to be used for selecting items from
   *   `array`.
   *
   *   Can be one of:
   *
   *   - `string`: The string is evaluated as an expression and the resulting value is used for substring match against
   *     the contents of the `array`. All strings or objects with string properties in `array` that contain this string
   *     will be returned. The predicate can be negated by prefixing the string with `!`.
   *
   *   - `Object`: A pattern object can be used to filter specific properties on objects contained
   *     by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items
   *     which have property `name` containing "M" and property `phone` containing "1". A special
   *     property name `$` can be used (as in `{$:"text"}`) to accept a match against any
   *     property of the object. That's equivalent to the simple substring match with a `string`
   *     as described above. The predicate can be negated by prefixing the string with `!`.
   *     For Example `{name: "!M"}` predicate will return an array of items which have property `name`
   *     not containing "M".
   *
   *   - `function(value)`: A predicate function can be used to write arbitrary filters. The function is
   *     called for each element of `array`. The final result is an array of those elements that
   *     the predicate returned true for.
   *
   * @param {function(actual, expected)|true|undefined} comparator Comparator which is used in
   *     determining if the expected value (from the filter expression) and actual value (from
   *     the object in the array) should be considered a match.
   *
   *   Can be one of:
   *
   *   - `function(actual, expected)`:
   *     The function will be given the object value and the predicate value to compare and
   *     should return true if the item should be included in filtered result.
   *
   *   - `true`: A shorthand for `function(actual, expected) { return angular.equals(expected, actual)}`.
   *     this is essentially strict comparison of expected and actual.
   *
   *   - `false|undefined`: A short hand for a function which will look for a substring match in case
   *     insensitive way.
   *
   * @example
     <example>
       <file name="index.html">
         <div ng-init="friends = [{name:'John', phone:'555-1276'},
                                  {name:'Mary', phone:'800-BIG-MARY'},
                                  {name:'Mike', phone:'555-4321'},
                                  {name:'Adam', phone:'555-5678'},
                                  {name:'Julie', phone:'555-8765'},
                                  {name:'Juliette', phone:'555-5678'}]"></div>
  
         Search: <input ng-model="searchText">
         <table id="searchTextResults">
           <tr><th>Name</th><th>Phone</th></tr>
           <tr ng-repeat="friend in friends | filter:searchText">
             <td>{{friend.name}}</td>
             <td>{{friend.phone}}</td>
           </tr>
         </table>
         <hr>
         Any: <input ng-model="search.$"> <br>
         Name only <input ng-model="search.name"><br>
         Phone only <input ng-model="search.phone"><br>
         Equality <input type="checkbox" ng-model="strict"><br>
         <table id="searchObjResults">
           <tr><th>Name</th><th>Phone</th></tr>
           <tr ng-repeat="friendObj in friends | filter:search:strict">
             <td>{{friendObj.name}}</td>
             <td>{{friendObj.phone}}</td>
           </tr>
         </table>
       </file>
       <file name="protractor.js" type="protractor">
         var expectFriendNames = function(expectedNames, key) {
           element.all(by.repeater(key + ' in friends').column(key + '.name')).then(function(arr) {
             arr.forEach(function(wd, i) {
               expect(wd.getText()).toMatch(expectedNames[i]);
             });
           });
         };
  
         it('should search across all fields when filtering with a string', function() {
           var searchText = element(by.model('searchText'));
           searchText.clear();
           searchText.sendKeys('m');
           expectFriendNames(['Mary', 'Mike', 'Adam'], 'friend');
  
           searchText.clear();
           searchText.sendKeys('76');
           expectFriendNames(['John', 'Julie'], 'friend');
         });
  
         it('should search in specific fields when filtering with a predicate object', function() {
           var searchAny = element(by.model('search.$'));
           searchAny.clear();
           searchAny.sendKeys('i');
           expectFriendNames(['Mary', 'Mike', 'Julie', 'Juliette'], 'friendObj');
         });
         it('should use a equal comparison when comparator is true', function() {
           var searchName = element(by.model('search.name'));
           var strict = element(by.model('strict'));
           searchName.clear();
           searchName.sendKeys('Julie');
           strict.click();
           expectFriendNames(['Julie'], 'friendObj');
         });
       </file>
     </example>
   */
  function filterFilter() {
    return function(array, expression, comparator) {
      if (!isArray(array)) return array;
  
      var comparatorType = typeof(comparator),
          predicates = [];
  
      predicates.check = function(value) {
        for (var j = 0; j < predicates.length; j++) {
          if(!predicates[j](value)) {
            return false;
          }
        }
        return true;
      };
  
      if (comparatorType !== 'function') {
        if (comparatorType === 'boolean' && comparator) {
          comparator = function(obj, text) {
            return angular.equals(obj, text);
          };
        } else {
          comparator = function(obj, text) {
            if (obj && text && typeof obj === 'object' && typeof text === 'object') {
              for (var objKey in obj) {
                if (objKey.charAt(0) !== '$' && hasOwnProperty.call(obj, objKey) &&
                    comparator(obj[objKey], text[objKey])) {
                  return true;
                }
              }
              return false;
            }
            text = (''+text).toLowerCase();
            return (''+obj).toLowerCase().indexOf(text) > -1;
          };
        }
      }
  
      var search = function(obj, text){
        if (typeof text == 'string' && text.charAt(0) === '!') {
          return !search(obj, text.substr(1));
        }
        switch (typeof obj) {
          case "boolean":
          case "number":
          case "string":
            return comparator(obj, text);
          case "object":
            switch (typeof text) {
              case "object":
                return comparator(obj, text);
              default:
                for ( var objKey in obj) {
                  if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) {
                    return true;
                  }
                }
                break;
            }
            return false;
          case "array":
            for ( var i = 0; i < obj.length; i++) {
              if (search(obj[i], text)) {
                return true;
              }
            }
            return false;
          default:
            return false;
        }
      };
      switch (typeof expression) {
        case "boolean":
        case "number":
        case "string":
          // Set up expression object and fall through
          expression = {$:expression};
          // jshint -W086
        case "object":
          // jshint +W086
          for (var key in expression) {
            (function(path) {
              if (typeof expression[path] === 'undefined') return;
              predicates.push(function(value) {
                return search(path == '$' ? value : (value && value[path]), expression[path]);
              });
            })(key);
          }
          break;
        case 'function':
          predicates.push(expression);
          break;
        default:
          return array;
      }
      var filtered = [];
      for ( var j = 0; j < array.length; j++) {
        var value = array[j];
        if (predicates.check(value)) {
          filtered.push(value);
        }
      }
      return filtered;
    };
  }
  