text: "use strict";
  
  function _typeof(obj) { return obj && typeof Symbol !== "undefined" && obj.constructor === Symbol ? "symbol" : typeof obj; }
  
  var _ = require('lodash');
  
  var EJSON = {}; // Global!
  var customTypes = {};
  // Add a custom type, using a method of your choice to get to and
  // from a basic JSON-able representation.  The factory argument
  // is a function of JSON-able --> your object
  // The type you add must have:
  // - A clone() method, so that Meteor can deep-copy it when necessary.
  // - A equals() method, so that Meteor can compare it
  // - A toJSONValue() method, so that Meteor can serialize it
  // - a typeName() method, to show how to look it up in our type table.
  // It is okay if these methods are monkey-patched on.
  EJSON.addType = function (name, factory) {
    if (_.has(customTypes, name)) throw new Error("Type " + name + " already present");
    customTypes[name] = factory;
  };
  
  var builtinConverters = [{ // Date
    matchJSONValue: function matchJSONValue(obj) {
      return _.has(obj, '$date') && _.size(obj) === 1;
    },
    matchObject: function matchObject(obj) {
      return obj instanceof Date;
    },
    toJSONValue: function toJSONValue(obj) {
      return { $date: obj.getTime() };
    },
    fromJSONValue: function fromJSONValue(obj) {
      return new Date(obj.$date);
    }
  }, { // Binary
    matchJSONValue: function matchJSONValue(obj) {
      return _.has(obj, '$binary') && _.size(obj) === 1;
    },
    matchObject: function matchObject(obj) {
      return typeof Uint8Array !== 'undefined' && obj instanceof Uint8Array || obj && _.has(obj, '$Uint8ArrayPolyfill');
    },
    toJSONValue: function toJSONValue(obj) {
      return { $binary: EJSON._base64Encode(obj) };
    },
    fromJSONValue: function fromJSONValue(obj) {
      return EJSON._base64Decode(obj.$binary);
    }
  }, { // Escaping one level
    matchJSONValue: function matchJSONValue(obj) {
      return _.has(obj, '$escape') && _.size(obj) === 1;
    },
    matchObject: function matchObject(obj) {
      if (_.isEmpty(obj) || _.size(obj) > 2) {
        return false;
      }
      return _.any(builtinConverters, function (converter) {
        return converter.matchJSONValue(obj);
      });
    },
    toJSONValue: function toJSONValue(obj) {
      var newObj = {};
      _.each(obj, function (value, key) {
        newObj[key] = EJSON.toJSONValue(value);
      });
      return { $escape: newObj };
    },
    fromJSONValue: function fromJSONValue(obj) {
      var newObj = {};
      _.each(obj.$escape, function (value, key) {
        newObj[key] = EJSON.fromJSONValue(value);
      });
      return newObj;
    }
  }, { // Custom
    matchJSONValue: function matchJSONValue(obj) {
      return _.has(obj, '$type') && _.has(obj, '$value') && _.size(obj) === 2;
    },
    matchObject: function matchObject(obj) {
      return EJSON._isCustomType(obj);
    },
    toJSONValue: function toJSONValue(obj) {
      return { $type: obj.typeName(), $value: obj.toJSONValue() };
    },
    fromJSONValue: function fromJSONValue(obj) {
      var typeName = obj.$type;
      var converter = customTypes[typeName];
      return converter(obj.$value);
    }
  }];
  
  EJSON._isCustomType = function (obj) {
    return obj && typeof obj.toJSONValue === 'function' && typeof obj.typeName === 'function' && _.has(customTypes, obj.typeName());
  };
  
  //for both arrays and objects, in-place modification.
  var adjustTypesToJSONValue = EJSON._adjustTypesToJSONValue = function (obj) {
    if (obj === null) return null;
    var maybeChanged = toJSONValueHelper(obj);
    if (maybeChanged !== undefined) return maybeChanged;
    _.each(obj, function (value, key) {
      if ((typeof value === "undefined" ? "undefined" : _typeof(value)) !== 'object' && value !== undefined) return; // continue
      var changed = toJSONValueHelper(value);
      if (changed) {
        obj[key] = changed;
        return; // on to the next key
      }
      // if we get here, value is an object but not adjustable
      // at this level.  recurse.
      adjustTypesToJSONValue(value);
    });
    return obj;
  };
  
  // Either return the JSON-compatible version of the argument, or undefined (if
  // the item isn't itself replaceable, but maybe some fields in it are)
  var toJSONValueHelper = function toJSONValueHelper(item) {
    for (var i = 0; i < builtinConverters.length; i++) {
      var converter = builtinConverters[i];
      if (converter.matchObject(item)) {
        return converter.toJSONValue(item);
      }
    }
    return undefined;
  };
  
  EJSON.toJSONValue = function (item) {
    var changed = toJSONValueHelper(item);
    if (changed !== undefined) return changed;
    if ((typeof item === "undefined" ? "undefined" : _typeof(item)) === 'object') {
      item = EJSON.clone(item);
      adjustTypesToJSONValue(item);
    }
    return item;
  };
  
  //for both arrays and objects. Tries its best to just
  // use the object you hand it, but may return something
  // different if the object you hand it itself needs changing.
  var adjustTypesFromJSONValue = EJSON._adjustTypesFromJSONValue = function (obj) {
    if (obj === null) return null;
    var maybeChanged = fromJSONValueHelper(obj);
    if (maybeChanged !== obj) return maybeChanged;
    _.each(obj, function (value, key) {
      if ((typeof value === "undefined" ? "undefined" : _typeof(value)) === 'object') {
        var changed = fromJSONValueHelper(value);
        if (value !== changed) {
          obj[key] = changed;
          return;
        }
        // if we get here, value is an object but not adjustable
        // at this level.  recurse.
        adjustTypesFromJSONValue(value);
      }
    });
    return obj;
  };
  
  // Either return the argument changed to have the non-json
  // rep of itself (the Object version) or the argument itself.
  
  // DOES NOT RECURSE.  For actually getting the fully-changed value, use
  // EJSON.fromJSONValue
  var fromJSONValueHelper = function fromJSONValueHelper(value) {
    if ((typeof value === "undefined" ? "undefined" : _typeof(value)) === 'object' && value !== null) {
      if (_.size(value) <= 2 && _.all(value, function (v, k) {
        return typeof k === 'string' && k.substr(0, 1) === '$';
      })) {
        for (var i = 0; i < builtinConverters.length; i++) {
          var converter = builtinConverters[i];
          if (converter.matchJSONValue(value)) {
            return converter.fromJSONValue(value);
          }
        }
      }
    }
    return value;
  };
  
  EJSON.fromJSONValue = function (item) {
    var changed = fromJSONValueHelper(item);
    if (changed === item && (typeof item === "undefined" ? "undefined" : _typeof(item)) === 'object') {
      item = EJSON.clone(item);
      adjustTypesFromJSONValue(item);
      return item;
    } else {
      return changed;
    }
  };
  
  EJSON.stringify = function (item) {
    return JSON.stringify(EJSON.toJSONValue(item));
  };
  
  EJSON.parse = function (item) {
    return EJSON.fromJSONValue(JSON.parse(item));
  };
  
  EJSON.isBinary = function (obj) {
    return typeof Uint8Array !== 'undefined' && obj instanceof Uint8Array || obj && obj.$Uint8ArrayPolyfill;
  };
  
  EJSON.equals = function (a, b, options) {
    var i;
    var keyOrderSensitive = !!(options && options.keyOrderSensitive);
    if (a === b) return true;
    if (!a || !b) // if either one is falsy, they'd have to be === to be equal
      return false;
    if (!((typeof a === "undefined" ? "undefined" : _typeof(a)) === 'object' && (typeof b === "undefined" ? "undefined" : _typeof(b)) === 'object')) return false;
    if (a instanceof Date && b instanceof Date) return a.valueOf() === b.valueOf();
    if (EJSON.isBinary(a) && EJSON.isBinary(b)) {
      if (a.length !== b.length) return false;
      for (i = 0; i < a.length; i++) {
        if (a[i] !== b[i]) return false;
      }
      return true;
    }
    if (typeof a.equals === 'function') return a.equals(b, options);
    if (a instanceof Array) {
      if (!(b instanceof Array)) return false;
      if (a.length !== b.length) return false;
      for (i = 0; i < a.length; i++) {
        if (!EJSON.equals(a[i], b[i], options)) return false;
      }
      return true;
    }
    // fall back to structural equality of objects
    var ret;
    if (keyOrderSensitive) {
      var bKeys = [];
      _.each(b, function (val, x) {
        bKeys.push(x);
      });
      i = 0;
      ret = _.all(a, function (val, x) {
        if (i >= bKeys.length) {
          return false;
        }
        if (x !== bKeys[i]) {
          return false;
        }
        if (!EJSON.equals(val, b[bKeys[i]], options)) {
          return false;
        }
        i++;
        return true;
      });
      return ret && i === bKeys.length;
    } else {
      i = 0;
      ret = _.all(a, function (val, key) {
        if (!_.has(b, key)) {
          return false;
        }
        if (!EJSON.equals(val, b[key], options)) {
          return false;
        }
        i++;
        return true;
      });
      return ret && _.size(b) === i;
    }
  };
  
  EJSON.clone = function (v) {
    var ret;
    if ((typeof v === "undefined" ? "undefined" : _typeof(v)) !== "object") return v;
    if (v === null) return null; // null has typeof "object"
    if (v instanceof Date) return new Date(v.getTime());
    if (EJSON.isBinary(v)) {
      ret = EJSON.newBinary(v.length);
      for (var i = 0; i < v.length; i++) {
        ret[i] = v[i];
      }
      return ret;
    }
    if (_.isArray(v) || _.isArguments(v)) {
      // For some reason, _.map doesn't work in this context on Opera (weird test
      // failures).
      ret = [];
      for (i = 0; i < v.length; i++) {
        ret[i] = EJSON.clone(v[i]);
      }return ret;
    }
    // handle general user-defined typed Objects if they have a clone method
    if (typeof v.clone === 'function') {
      return v.clone();
    }
    // handle other objects
    ret = {};
    _.each(v, function (value, key) {
      ret[key] = EJSON.clone(value);
    });
    return ret;
  };
  
  module.exports = EJSON;