"use strict";

var defProp = Object.defineProperty || function(obj, name, desc) {
  // Normal property assignment is the best we can do if
  // Object.defineProperty is not available.
  obj[name] = desc.value;
};

// For functions that will be invoked using .call or .apply, we need to
// define those methods on the function objects themselves, rather than
// inheriting them from Function.prototype, so that a malicious or clumsy
// third party cannot interfere with the functionality of this module by
// redefining Function.prototype.call or .apply.
function makeSafeToCall(fun) {
  defProp(fun, "call", { value: fun.call });
  defProp(fun, "apply", { value: fun.apply });
  return fun;
}

var hasOwn = makeSafeToCall(Object.prototype.hasOwnProperty);
var numToStr = makeSafeToCall(Number.prototype.toString);
var strSlice = makeSafeToCall(String.prototype.slice);

var cloner = function(){};
var create = Object.create || function(prototype, properties) {
  cloner.prototype = prototype || null;
  var obj = new cloner;

  // The properties parameter is unused by this module, but I want this
  // shim to be as complete as possible.
  if (properties)
    for (var name in properties)
      if (hasOwn.call(properties, name))
        defProp(obj, name, properties[name]);

  return obj;
};

var rand = Math.random;
var uniqueKeys = create(null);

function makeUniqueKey() {
  // Collisions are highly unlikely, but this module is in the business of
  // making guarantees rather than safe bets.
  do var uniqueKey = strSlice.call(numToStr.call(rand(), 36), 2);
  while (hasOwn.call(uniqueKeys, uniqueKey));
  return uniqueKeys[uniqueKey] = uniqueKey;
}

// External users might find this function useful, but it is not necessary
// for the typical use of this module.
defProp(exports, "makeUniqueKey", {
  value: makeUniqueKey
});

function wrap(obj, value) {
  var old = obj[value.name];
  defProp(obj, value.name, { value: value });
  return old;
}

// Object.getOwnPropertyNames is the only way to enumerate non-enumerable
// properties, so if we wrap it to ignore our secret keys, there should be
// no way (except guessing) to access those properties.
var realGetOPNs = wrap(Object, function getOwnPropertyNames(object) {
  for (var names = realGetOPNs(object),
           src = 0,
           dst = 0,
           len = names.length;
       src < len;
       ++src) {
    if (!hasOwn.call(uniqueKeys, names[src])) {
      if (src > dst) {
        names[dst] = names[src];
      }
      ++dst;
    }
  }
  names.length = dst;
  return names;
});

function defaultCreatorFn(object) {
  return create(null);
}

function makeAccessor(secretCreatorFn) {
  var brand = makeUniqueKey();
  var passkey = create(null);

  secretCreatorFn = secretCreatorFn || defaultCreatorFn;

  function register(object) {
    var secret; // Created lazily.

    function vault(key, forget) {
      // Only code that has access to the passkey can retrieve (or forget)
      // the secret object.
      if (key === passkey) {
        return forget
          ? secret = null
          : secret || (secret = secretCreatorFn(object));
      }
    }

    defProp(object, brand, { value: vault });
  }

  function accessor(object) {
    if (!hasOwn.call(object, brand))
      register(object);
    return object[brand](passkey);
  }

  accessor.forget = function(object) {
    if (hasOwn.call(object, brand))
      object[brand](passkey, true);
  };

  return accessor;
}

defProp(exports, "makeAccessor", {
  value: makeAccessor
});
