'use strict';

var operators = require('./operators');

/**
 * Strip MongoDB operators from user provided objects.
 *
 * @Constructor
 * @param {Object} options
 * @api public
 */
function Operator(options) {
  this.options = options || {};

  for (var group in operators) {
    var prop = group.toLowerCase();
    this[prop] = this.mask(group, this.options[prop]);
  }
}

/**
 * Filter the provided query object, removing keys that are not
 * allowed to exists on the query object.
 *
 * @param {Object} query Query object.
 * @param {String} restrict Operator group to further restrict allowed operators.
 * @return {Object} Filtered query object.
 * @api public
 */
Operator.prototype.filter = function filter(query, restrict) {
  if ('object' !== typeof query) return query;

  var map = this.groups(restrict);

  for (var key in query) {
    if ('object' === typeof query[key]) {
      this.parse(query[key]);
      continue;
    }

    for (var group in map) {
      if (!~this[group].indexOf(key)) delete query[key];
    }
  }

  return query;
};

/**
 * Enable certain operators by providing a bitmask against the group.
 *
 * @param {String} group Group of operators to check the bitmask against.
 * @param {Number} bitmask Binary mask.
 * @returns {Array} set of allowed operators.
 * @api public
 */
Operator.prototype.mask = function mask(group, bitmask) {
  if (!group || !bitmask) return [];
  var stack = '';

  for (var key in operators[group]) {
    if (key !== 'ALL' && Operator[group][key] & bitmask) {
      stack += operators[group][key];
    }
  }

  return stack.split(',');
};

/**
 * Return the operator groups that have allowed operators,
 * optionally filtered by a group name.
 *
 * @param {String} restrict Optional group name to only return that group.
 * @return {Array} Set of group names.
 * @api public
 */
Operator.prototype.groups = function groups(restrict) {
  var operator = this;

  return Object.keys(operators).map(function lowercase(group) {
    group = group.toLowerCase();
    if (operator[group].length && group === restrict) return group;
  }).filter(Boolean);
};

/**
 * Check if the operator is allowed for the group.
 *
 * @param {String} group Group the operator belongs to.
 * @param {String} key Operator name
 * @return {Boolean}
 * @api public
 */
Operator.prototype.allowed = function allowed(group, key) {
  key += key[0] !== '$' ? '$' : '';
  return !!~this[group].indexOf(key);
};

//
// Define bitmaps for all operators on the constructor.
//
for (var group in operators) {
  var i = 0;

  if ('object' !== typeof operators[group]) Operator[group] = 1 << i++;
  else for (var key in operators[group]) {
    Operator[group] = Operator[group] || Object.create(null);
    Operator[group][key] = 1 << i++;
  }

  Operator[group].ALL = (1 << i) - 1;
}

//
// Expose the module.
//
module.exports = Operator;
@@ -46,16 +46,37 @@
 rators')
+%0A  , custom = %5B'ALL'%5D
 ;%0A%0A/**%0A 
@@ -203,23 +203,21 @@
 unction 
-Operato
+Filte
 r(option
@@ -673,39 +673,37 @@
 @api public%0A */%0A
-Operato
+Filte
 r.prototype.filt
@@ -796,19 +796,22 @@
 %0A%0A  var 
-map
+groups
  = this.
@@ -826,16 +826,36 @@
 estrict)
+%0A    , filter = this
 ;%0A%0A  for
@@ -872,24 +872,58 @@
 in query) %7B%0A
+    if ('$' !== key%5B0%5D) continue;%0A
     if ('obj
@@ -1013,28 +1013,41 @@
     
-for (var group in ma
+groups.forEach(function each(grou
 p) %7B
@@ -1059,20 +1059,22 @@
   if (!~
-this
+filter
 %5Bgroup%5D.
@@ -1103,32 +1103,34 @@
 uery%5Bkey%5D;%0A    %7D
+);
 %0A  %7D%0A%0A  return q
@@ -1387,39 +1387,37 @@
 @api public%0A */%0A
-Operato
+Filte
 r.prototype.mask
@@ -1557,32 +1557,53 @@
 if (
-key !== 'ALL' && Operato
+~custom.indexOf(key)) continue;%0A    if (Filte
 r%5Bgr
@@ -1936,39 +1936,37 @@
 @api public%0A */%0A
-Operato
+Filte
 r.prototype.grou
@@ -2004,23 +2004,21 @@
 %7B%0A  var 
-operato
+filte
 r = this
@@ -2129,33 +2129,102 @@
 if (
-operator%5Bgroup%5D.length &&
+filter%5Bgroup%5D.length) return group;%0A  %7D).filter(function filter(group) %7B%0A    return restrict ?
  gro
@@ -2238,24 +2238,18 @@
 restrict
-) return
+ :
  group;%0A
@@ -2256,24 +2256,8 @@
   %7D)
-.filter(Boolean)
 ;%0A%7D;
@@ -2449,23 +2449,21 @@
 lic%0A */%0A
-Operato
+Filte
 r.protot
@@ -2736,23 +2736,21 @@
 group%5D) 
-Operato
+Filte
 r%5Bgroup%5D
@@ -2805,31 +2805,29 @@
 oup%5D) %7B%0A    
-Operato
+Filte
 r%5Bgroup%5D = O
@@ -2825,23 +2825,21 @@
 roup%5D = 
-Operato
+Filte
 r%5Bgroup%5D
@@ -2867,23 +2867,21 @@
 l);%0A    
-Operato
+Filte
 r%5Bgroup%5D
@@ -2905,23 +2905,21 @@
 %0A  %7D%0A%0A  
-Operato
+Filte
 r%5Bgroup%5D
@@ -2987,17 +2987,15 @@
 ports = 
-Operato
+Filte
 r;

rename filter