text: "use strict";
  
  var Cache = require("./cache.js").Cache;
  var tuple = require("immutable-tuple").tuple;
  var Entry = require("./entry.js").Entry;
  var getLocal = require("./local.js").get;
  
  // Exported so that custom makeCacheKey functions can easily reuse the
  // default implementation (with different arguments).
  exports.defaultMakeCacheKey = tuple;
  
  function normalizeOptions(options) {
    options = options || Object.create(null);
  
    if (typeof options.makeCacheKey !== "function") {
      options.makeCacheKey = tuple;
    }
  
    if (typeof options.max !== "number") {
      options.max = Math.pow(2, 16);
    }
  
    return options;
  }
  
  function wrap(fn, options) {
    options = normalizeOptions(options);
  
    // If this wrapped function is disposable, then its creator does not
    // care about its return value, and it should be removed from the cache
    // immediately when it no longer has any parents that depend on it.
    var disposable = !! options.disposable;
  
    var cache = new Cache({
      max: options.max,
      dispose: function (key, entry) {
        entry.dispose();
      }
    });
  
    function reportOrphan(entry) {
      if (disposable) {
        // Triggers the entry.dispose() call above.
        cache.delete(entry.key);
        return true;
      }
    }
  
    function optimistic() {
      if (disposable && ! getLocal().currentParentEntry) {
        // If there's no current parent computation, and this wrapped
        // function is disposable (meaning we don't care about entry.value,
        // just dependency tracking), then we can short-cut everything else
        // in this function, because entry.recompute() is going to recycle
        // the entry object without recomputing anything, anyway.
        return;
      }
  
      var key = options.makeCacheKey.apply(null, arguments);
      if (! key) {
        return fn.apply(null, arguments);
      }
  
      var args = [], len = arguments.length;
      while (len--) args[len] = arguments[len];
  
      var entry = cache.get(key);
      if (entry) {
        entry.args = args;
      } else {
        cache.set(key, entry = Entry.acquire(fn, key, args));
        entry.subscribe = options.subscribe;
        if (disposable) {
          entry.reportOrphan = reportOrphan;
        }
      }
  
      var value = entry.recompute();
  
      // Move this entry to the front of the least-recently used queue,
      // since we just finished computing its value.
      cache.set(key, entry);
  
      // Clean up any excess entries in the cache, but only if this entry
      // has no parents, which means we're not in the middle of a larger
      // computation that might be flummoxed by the cleaning.
      if (entry.parents.size === 0) {
        cache.clean();
      }
  
      // If options.disposable is truthy, the caller of wrap is telling us
      // they don't care about the result of entry.recompute(), so we should
      // avoid returning the value, so it won't be accidentally used.
      if (! disposable) {
        return value;
      }
    }
  
    optimistic.dirty = function () {
      var key = options.makeCacheKey.apply(null, arguments);
      if (! key) {
        return;
      }
  
      if (! cache.has(key)) {
        return;
      }
  
      cache.get(key).setDirty();
    };
  
    return optimistic;
  }
  
  exports.wrap = wrap;
  