//////////////////// MIXINS

// Minimal, well-documented, general-purpose CSS mixins.
// (Some are same as Bootstrap.)

////////// Box-Sizing: Border-Box

// Setting `box-sizing: border-box` on an element causes the CSS
// layout algorithm to interpret `width` and `height` declarations
// as referring to the size of the border box (outside the border),
// not the content box as usual (inside the padding).
//
// This is especially useful for stretching a form element to the
// width of its container even if the form element has arbitrary
// padding and borders, which can be done using `width: 100%`.
//
// Browser support is IE 8+ and all modern browsers, with the caveat
// that `-moz-box-sizing` in Firefox is considered to have some
// buggy or non-compliant behavior.  For example, min/max-width/height
// may not interact correctly. See
// https://bugzilla.mozilla.org/show_bug.cgi?id=243412.
.box-sizing-by-border () {
  -webkit-box-sizing: border-box;
  -moz-box-sizing: border-box;
  box-sizing: border-box;
}

////////// Box-Shadow

.box-shadow (...) {
  box-shadow: @arguments;
  -webkit-box-shadow: @arguments; // For Android
}

////////// Display: Inline-block

.display-inline-block () {
  display: inline-block;

  // IE 7 hacks (disabled)
  //*display: inline;
  //*zoom: 1;
}

////////// Gradients

.vertical-gradient (@topColor: #fff, @bottomColor: #000) {
  // Fallback in absence of gradients
  background-color: mix(@topColor, @bottomColor, 60%);
  // FF 3.6+
  background-image: -moz-linear-gradient(top, @topColor, @bottomColor);
  // Safari 4+, Chrome 2+
  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(@topColor), to(@bottomColor));
  // Safari 5.1+, Chrome 10+
  background-image: -webkit-linear-gradient(top, @topColor, @bottomColor);
  // Opera 11.10
  background-image: -o-linear-gradient(top, @topColor, @bottomColor);
  // Standard, IE10
  background-image: linear-gradient(to bottom, @topColor, @bottomColor);
  background-repeat: repeat-x;
  // IE9 and down
  // XXX This gradient hack causes gradients to overflow the rounded corners
  // in IE9.  We make the same call as Bootstrap here: keep the rounded
  // corners and withhold the gradients.
  //  filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",argb(@topColor),argb(@bottomColor)));
}

.reset-ie-gradient () {
  filter: e(%("progid:DXImageTransform.Microsoft.gradient(enabled = false)"));
}

////////// Unselectable

.unselectable () {
  -webkit-user-select: none; // Chrome/Safari
  -moz-user-select: none; // Firefox
  -ms-user-select: none; // IE10+

  // These delarations not implemented in browsers yet:
  -o-user-select: none;
  user-select: none;

  // In IE <= 9 and Opera, need unselectable="on" in the HTML.
}

//////////////////// LOGIN BUTTONS

@login-buttons-accounts-dialog-width: 198px;
#login-buttons {

  .display-inline-block();
  margin-right: 0.2px; // Fixes display on IE8: http://www.compsoft.co.uk/Blog/2009/11/inline-block-not-quite-inline-blocking.html

  // This seems to keep the height of the line from
  // being sensitive to the presence of the unicode down arrow,
  // which otherwise bumps the baseline down by 1px.
  line-height: 1;

  .login-button {
    position: relative; // so that we can position the image absolutely within the button
  }

  .login-buttons-with-only-one-button {
    display: inline-block;
    .login-button { display: inline-block; }
    .login-text-and-button {
      display: inline-block;
    }
  }
  .login-display-name {
    display: inline-block;
    padding-right: 2px;
    line-height: 1.5;
    font-family: 'Helvetica Neue', Helvetica, Arial, default;
  }
  .loading {
    line-height: 1;
    background-image: url(data:image/gif;base64,R0lGODlhEAALAPQAAP///wAAANra2tDQ0Orq6gYGBgAAAC4uLoKCgmBgYLq6uiIiIkpKSoqKimRkZL6+viYmJgQEBE5OTubm5tjY2PT09Dg4ONzc3PLy8ra2tqCgoMrKyu7u7gAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCwAAACwAAAAAEAALAAAFLSAgjmRpnqSgCuLKAq5AEIM4zDVw03ve27ifDgfkEYe04kDIDC5zrtYKRa2WQgAh+QQJCwAAACwAAAAAEAALAAAFJGBhGAVgnqhpHIeRvsDawqns0qeN5+y967tYLyicBYE7EYkYAgAh+QQJCwAAACwAAAAAEAALAAAFNiAgjothLOOIJAkiGgxjpGKiKMkbz7SN6zIawJcDwIK9W/HISxGBzdHTuBNOmcJVCyoUlk7CEAAh+QQJCwAAACwAAAAAEAALAAAFNSAgjqQIRRFUAo3jNGIkSdHqPI8Tz3V55zuaDacDyIQ+YrBH+hWPzJFzOQQaeavWi7oqnVIhACH5BAkLAAAALAAAAAAQAAsAAAUyICCOZGme1rJY5kRRk7hI0mJSVUXJtF3iOl7tltsBZsNfUegjAY3I5sgFY55KqdX1GgIAIfkECQsAAAAsAAAAABAACwAABTcgII5kaZ4kcV2EqLJipmnZhWGXaOOitm2aXQ4g7P2Ct2ER4AMul00kj5g0Al8tADY2y6C+4FIIACH5BAkLAAAALAAAAAAQAAsAAAUvICCOZGme5ERRk6iy7qpyHCVStA3gNa/7txxwlwv2isSacYUc+l4tADQGQ1mvpBAAIfkECQsAAAAsAAAAABAACwAABS8gII5kaZ7kRFGTqLLuqnIcJVK0DeA1r/u3HHCXC/aKxJpxhRz6Xi0ANAZDWa+kEAA7AAAAAAAAAAAA);
    width: 16px;
    background-position: center center;
    background-repeat: no-repeat;
  }
}

#login-buttons .login-button, .accounts-dialog .login-button {
  cursor: pointer;
  .unselectable();
  padding: 4px 8px;

  font-size: 80%;
  font-family: 'Helvetica Neue', Helvetica, Arial, default;
  line-height: 1.5;

  text-align: center;
  color: #fff;
  text-shadow: 0px -1px 1px rgba(0, 0, 0, 0.5);

  @topColor: #a5acc9;
  @bottomColor: darken(@topColor, 25%);

  .vertical-gradient(@topColor, @bottomColor);
  border-radius: 4px;
  border: 1px solid mix(@bottomColor, #000, 30%);
  .box-shadow(0 1px 3px rgba(0,0,0,0.5));

  &:active {
    .box-shadow(none);
    .vertical-gradient(mix(@bottomColor, @topColor, 30%),
                       mix(@bottomColor, #000, 80%));
  }

  &.login-button-disabled, &.login-button-disabled:active {
    color: #ddd;
    .box-shadow(none);
    background: #999;
  }
}

// precendence of this selector is significant
.accounts-dialog * {
  // A base for our dialog CSS, to reset browser styles and protect against
  // the app's CSS.  Dialogs include the dropdown, config modals, and the
  // reset password modal.  We can't completely isolate the dialogs from
  // the app's CSS, and that isn't the goal because the app can style them.
  // This rule is a compromise that should take precedence over some very
  // broad rules but be overridden by more specific ones.

  // Add more declarations here if they help the dialogs look good
  // out-of-the-box in more apps.

  padding: 0;
  margin: 0;
  line-height: inherit;
  color: inherit;
  font: inherit;
}

.accounts-dialog .login-button {
  width: auto;
  margin-bottom: 4px;
}

#login-buttons {
  .login-buttons-padding {
    display: inline-block;
    width: 30px;
  }

  .login-display-name { margin-right: 4px; }
  .configure-button {

    .vertical-gradient(#f53, darken(#f53, 15%));
    .box-shadow(0 1px 3px rgba(0,0,0,0.5));

    &:active { background: #b10; .box-shadow(0 1px 3px rgba(0,0,0,0.5) inset); }
  }

  .login-image {
    display: inline-block;
    position: absolute;
    left: 6px;
    top: 6px;
    width: 16px;
    height: 16px;
  }

  .text-besides-image {
    margin-left: 18px;
  }

  .no-services { color: red; }

  .login-link-and-dropdown-list {
    position: relative;
  }
  .login-close-text {
    float: left;
    position: relative;
    padding-bottom: 8px;
  }

  .login-text-and-button .loading, .login-link-and-dropdown-list .loading {
    display: inline-block;
  }
  &.login-buttons-dropdown-align-left #login-dropdown-list .loading {
    float: right;
  }
  &.login-buttons-dropdown-align-right #login-dropdown-list .loading {
    float: left;
  }

  .login-close-text-clear { clear: both; }

  .or { text-align: center; }
  .hline { text-decoration: line-through; color: lightgrey; }
  .or-text { font-weight: bold; }

  #signup-link { float: right; }
  #forgot-password-link { float: left; }
  #back-to-login-link { float: right; }
}

#login-buttons a, .accounts-dialog a {
    cursor: pointer;
    text-decoration: underline;
}

#login-buttons.login-buttons-dropdown-align-right .login-close-text {
    float: right;
}

@meteor-accounts-base-padding: 8px;
@meteor-accounts-dialog-border-width: 1px;

.accounts-dialog {
  border: @meteor-accounts-dialog-border-width solid #777;
  z-index: 1000;
  background: white;

  .box-shadow(0 3px 6px 1px rgba(0, 0, 0, 0.3));
  border-radius: 4px;

  padding: 8px 12px;
  margin: -8px -12px 0 -12px;

  width: @login-buttons-accounts-dialog-width;

  // Labels and links inherit app's font with this line commented out:
  //font-family: 'Helvetica Neue', Helvetica, Arial, default;
  font-size: 16px;
  color: #333;

  // XXX Make the dropdown and dialogs look good without a top-level
  // line-height: 1.6.  For now, we apply it to everything except
  // the "Close" link, which we want to have the same line-height
  // as the "Sign in" link.
  & > * { line-height: 1.6; }
  & > .login-close-text { line-height: inherit; }

  label, .title {
    font-weight: bold;
    font-size: 80%;
  }
  input {
    // Be pixel-accurate in IE 8+ regardless of our borders and
    // paddings, at the expense of IE 7.
    // Any heights or widths applied to this element will set the
    // size of the border box (including padding and borders)
    // instead of the content box.  This makes it possible to
    // do width 100%.
    .box-sizing-by-border();
    width: 100%;
    // A fix purely for the "meteor add bootstrap" experience.
    // Bootstrap sets "height: 20px" on form fields, which is too
    // small when applied to the border box.  People have complained
    // that Bootstrap takes this approach for the sake of IE 7:
    // https://github.com/twitter/bootstrap/issues/2935
    // Our work-around is to override Bootstrap's rule (with higher
    // precedence).
    &[type] { height: auto; }
  }

  .login-button-form-submit { margin-top: 8px; }
  .message { font-size: 80%; margin-top: 2px; line-height: 1.3; }
  .error-message { color: red; }
  .info-message { color: green; }
  .additional-link { font-size: 75%; }

  .accounts-close {
    position: absolute;
    top: 0;
    right: 5px;

    font-size: 20px;
    font-weight: bold;
    line-height: 20px;
    text-decoration: none;
    color: #000;
    opacity: 0.4;

    &:hover {
      opacity: 0.8;
    }
  }

  #login-buttons-cancel-reset-password { float: right; }
  #login-buttons-cancel-enroll-account { float: right; }
}

#login-dropdown-list {
  position: absolute;
  // The top-left of the border-box of the dropdown is absolutely
  // positioned within its container, so we need to compensate
  // for the border.  The padding is already compensated for by
  // negative margins on the dropdown.
  // XXX We could use negative margins to compensate for the
  // border too.
  top: -@meteor-accounts-dialog-border-width;
  left: -@meteor-accounts-dialog-border-width;
}

#login-buttons.login-buttons-dropdown-align-right #login-dropdown-list {
  left: auto;
  right: -@meteor-accounts-dialog-border-width;
}

#login-buttons-message-dialog .message {
    /* we intentionally want it bigger on this dialog since it's the only thing displayed */
    font-size: 100%;
}

.accounts-centered-dialog {
  font-family: 'Helvetica Neue', Helvetica, Arial, default;

  z-index: 1001;
  position: fixed;

  left: 50%;
  margin-left: -(@login-buttons-accounts-dialog-width
               + @meteor-accounts-base-padding) / 2;

  top: 50%;
  margin-top: -40px; /* = approximately -height/2, though height can change */
}

@configure-login-service-dialog-width: 530px;

#configure-login-service-dialog {
  width: @configure-login-service-dialog-width;
  margin-left: -(@configure-login-service-dialog-width
               + @meteor-accounts-base-padding) / 2;
  margin-top: -220px; /* = approximately -height/2, though height can change */

  table { width: 100%; }
  input {
    width: 100%;
    font-family: "Courier New", Courier, monospace;
  }
  ol {
    margin-top: 10px;
    margin-bottom: 10px;

    li { margin-left: 30px; }
  }
  .configuration_labels { width: 30%; }
  .configuration_inputs { width: 70%; }
  .new-section { margin-top: 10px; }
  .url { font-family: "Courier New", Courier, monospace; }
}

#configure-login-service-dialog-save-configuration {
  float: right;
}

.configure-login-service-dismiss-button {
  float: left;
}

#just-verified-dismiss-button, #messages-dialog-dismiss-button {
  margin-top: 4px;
}

.hide-background {
  position: fixed;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  z-index: 999;

  /* XXX consider replacing with DXImageTransform */
  background-color: rgb(0.2, 0.2, 0.2); /* fallback for IE7-8 */

  background-color: rgba(0, 0, 0, 0.7);
}

#login-buttons, .accounts-dialog {
  input[type=text], input[type=email], input[type=password] {
    padding: 4px;
    border: 1px solid #999;
    border-radius: 3px;
    line-height: 1;
  }
}


var handle = coll.find(query).observeChanges({

polls[name] = (name in polls ? polls[name] + 1 : 1);

keep keep keep keep replace replace keep keep keep keep keep

// Make sure that we only poll on invalidation, not due to time, and

// keep track of when we do. Note: this option disables the use of

// oplogs (which admittedly is somewhat irrelevant to this feature).

/**
 * Provide a synchronous Collection API using fibers, backed by
 * MongoDB.  This is only for use on the server, and mostly identical
 * to the client API.
 *
 * NOTE: the public API methods must be run within a fiber. If you call
 * these outside of a fiber they will explode!
 */

var path = Npm.require('path');
var MongoDB = Npm.require('mongodb');
var Fiber = Npm.require('fibers');
var Future = Npm.require(path.join('fibers', 'future'));

MongoInternals = {};

var replaceNames = function (filter, thing) {
  if (typeof thing === "object") {
    if (_.isArray(thing)) {
      return _.map(thing, _.bind(replaceNames, null, filter));
    }
    var ret = {};
    _.each(thing, function (value, key) {
      ret[filter(key)] = replaceNames(filter, value);
    });
    return ret;
  }
  return thing;
};

var makeMongoLegal = function (name) { return "EJSON" + name; };
var unmakeMongoLegal = function (name) { return name.substr(5); };

var replaceMongoAtomWithMeteor = function (document) {
  if (document instanceof MongoDB.Binary) {
    var buffer = document.value(true);
    return new Uint8Array(buffer);
  }
  if (document instanceof MongoDB.ObjectID) {
    return new Meteor.Collection.ObjectID(document.toHexString());
  }
  if (document["EJSON$type"] && document["EJSON$value"]) {
    return EJSON.fromJSONValue(replaceNames(unmakeMongoLegal, document));
  }
  return undefined;
};

var replaceMeteorAtomWithMongo = function (document) {
  if (EJSON.isBinary(document)) {
    // This does more copies than we'd like, but is necessary because
    // MongoDB.BSON only looks like it takes a Uint8Array (and doesn't actually
    // serialize it correctly).
    return new MongoDB.Binary(new Buffer(document));
  }
  if (document instanceof Meteor.Collection.ObjectID) {
    return new MongoDB.ObjectID(document.toHexString());
  } else if (EJSON._isCustomType(document)) {
    return replaceNames(makeMongoLegal, EJSON.toJSONValue(document));
  }
  // It is not ordinarily possible to stick dollar-sign keys into mongo
  // so we don't bother checking for things that need escaping at this time.
  return undefined;
};

var replaceTypes = function (document, atomTransformer) {
  if (typeof document !== 'object' || document === null)
    return document;

  var replacedTopLevelAtom = atomTransformer(document);
  if (replacedTopLevelAtom !== undefined)
    return replacedTopLevelAtom;

  var ret = document;
  _.each(document, function (val, key) {
    var valReplaced = replaceTypes(val, atomTransformer);
    if (val !== valReplaced) {
      // Lazy clone. Shallow copy.
      if (ret === document)
        ret = _.clone(document);
      ret[key] = valReplaced;
    }
  });
  return ret;
};

MongoConnection = function (url, connectionOptions) {
  var self = this;
  connectionOptions = connectionOptions || {};
  self._connectCallbacks = [];
  self._liveResultsSets = {};

  var options = {db: {safe: true}};

  // Set autoReconnect to true, unless passed on the URL. Why someone
  // would want to set autoReconnect to false, I'm not really sure, but
  // keeping this for backwards compatibility for now.
  if (!(/[\?&]auto_?[rR]econnect=/.test(url))) {
    options.server = {auto_reconnect: true};
  }

  // Disable the native parser by default, unless specifically enabled
  // in the mongo URL.
  // - The native driver can cause errors which normally would be
  //   thrown, caught, and handled into segfaults that take down the
  //   whole app.
  // - Binary modules don't yet work when you bundle and move the bundle
  //   to a different platform (aka deploy)
  // We should revisit this after binary npm module support lands.
  if (!(/[\?&]native_?[pP]arser=/.test(url))) {
    options.db.native_parser = false;
  }

  MongoDB.connect(url, options, function(err, db) {
    if (err)
      throw err;
    self.db = db;

    Fiber(function () {
      // drain queue of pending callbacks
      _.each(self._connectCallbacks, function (c) {
        c(db);
      });
    }).run();
  });

  self._oplogHandle = null;
  // XXX we should NOT be reading directly from the env here (this should be an
  // argument to MongoConnection eg) but I want to wait for the AppConfig API to
  // settle a little before thinking too hard about this
  if (process.env.XXX_OPLOG_URL && !connectionOptions.isOplog) {
    var dbName = Npm.require('url').parse(url).pathname.substr(1);
    // Defer this, because it blocks. If we start observing cursors before the
    // oplog handle is ready, they just don't get to use the oplog.
    Meteor.defer(_.bind(self._startOplogTailing,
                        self, process.env.XXX_OPLOG_URL, dbName));
  }
};

MongoConnection.prototype.close = function() {
  var self = this;

  // XXX probably untested
  var oplogHandle = self._oplogHandle;
  self._oplogHandle = null;
  if (oplogHandle)
    oplogHandle.stop();

  // Use Future.wrap so that errors get thrown. This happens to
  // work even outside a fiber since the 'close' method is not
  // actually asynchronous.
  Future.wrap(_.bind(self.db.close, self.db))(true).wait();
};

MongoConnection.prototype._withDb = function (callback) {
  var self = this;
  if (self.db) {
    callback(self.db);
  } else {
    self._connectCallbacks.push(callback);
  }
};

// Returns the Mongo Collection object; may yield.
MongoConnection.prototype._getCollection = function (collectionName) {
  var self = this;

  var future = new Future;
  self._withDb(function (db) {
    db.collection(collectionName, future.resolver());
  });
  return future.wait();
};

MongoConnection.prototype._createCappedCollection = function (collectionName,
                                                              byteSize) {
  var self = this;
  var future = new Future();
  self._withDb(function (db) {
    db.createCollection(collectionName, {capped: true, size: byteSize},
                        future.resolver());
  });
  future.wait();
};

// This should be called synchronously with a write, to create a
// transaction on the current write fence, if any. After we can read
// the write, and after observers have been notified (or at least,
// after the observer notifiers have added themselves to the write
// fence), you should call 'committed()' on the object returned.
MongoConnection.prototype._maybeBeginWrite = function () {
  var self = this;
  var fence = DDPServer._CurrentWriteFence.get();
  if (fence)
    return fence.beginWrite();
  else
    return {committed: function () {}};
};

var OPLOG_COLLECTION = 'oplog.rs';

var WRITE_COLLECTION = 'meteor_livedata_Writes';
// XXX This is problematic if our RNG isn't seeded well enough.
var myServerId = Random.id();
var nextWriteId = 1;
// XXX doc
var outstandingWrites = [];

// Like Perl's quotemeta: quotes all regexp metacharacters. See
//   https://github.com/substack/quotemeta/blob/master/index.js
var quotemeta = function (str) {
    return String(str).replace(/(\W)/g, '\\$1');
};

MongoConnection.prototype._startOplogTailing = function (oplogUrl, dbName) {
  var self = this;

  var oplogConnection = new MongoConnection(oplogUrl, {isOplog: true});
  // Find the last oplog entry. Blocks until the connection is ready.

  var lastOplogEntry = oplogConnection.findOne(
    OPLOG_COLLECTION, {}, {sort: {$natural: -1}});

  var oplogSelector = {
    ns: new RegExp('^' + quotemeta(dbName) + '\\.'),
    $or: [
      {op: {$in: ['i', 'u', 'd']}},
      {op: 'c', 'o.drop': {$exists: true}}
    ]
  };
  if (lastOplogEntry)
    oplogSelector.ts = {$gt: lastOplogEntry.ts};

  var cursorDescription = new CursorDescription(
    OPLOG_COLLECTION, oplogSelector, {tailable: true});

  var callbacksByCollection = {};

  var processFence = function (doc) {
    if (doc.op !== 'i' && doc.op !== 'u')
      return;
    var serverId = (doc.op === 'i' ? doc.o._id : doc.o2._id);
    if (serverId !== myServerId)
      return;
    var writeId =
          (doc.op === 'i' ? doc.o.write : (doc.o.$set && doc.o.$set.write));
    if (typeof writeId !== 'number')
      return;
    // Process all writes up to this point.
    while (!_.isEmpty(outstandingWrites)
           && outstandingWrites[0].writeId <= writeId) {
      var write = outstandingWrites.shift();
      write.write.committed();
    }
  };

  self._oplogHandle = oplogConnection.tail(cursorDescription, function (doc) {
    if (!doc.ns && doc.ns.length > dbName.length + 1 &&
        doc.ns.substr(0, dbName.length + 1) === (dbName + '.'))
      throw new Error("Unexpected ns");

    var collectionName = doc.ns.substr(dbName.length + 1);

    if (collectionName === WRITE_COLLECTION) {
      processFence(doc);
      return;
    }

    _.each(callbacksByCollection[collectionName], function (callback) {
      callback(EJSON.clone(doc));
    });
  });

  var nextId = 0;
  self._oplogHandle.onOplogEntry = function (collectionName, callback) {
    callback = Meteor.bindEnvironment(callback, function (err) {
      Meteor._debug("Error in oplog callback", err.stack);
    });
    if (!_.has(callbacksByCollection, collectionName))
      callbacksByCollection[collectionName] = {};
    var callbackId = nextId++;
    callbacksByCollection[collectionName][callbackId] = callback;
    return {
      stop: function () {
        delete callbacksByCollection[collectionName][callbackId];
      }
    };
  };
};

MongoConnection.prototype._observeChangesWithOplog = function (
  cursorDescription, callbacks) {
  var self = this;

  // XXX let's do this with race conditions first!
  //
  // the real way will involve special oplog handling during the initial cursor
  // read. specifically:
  //
  // 1) start reading the oplog. for every document that could conceivably be
  // relevant, cache a bit of information about what we saw.  (eg, cache
  // document for inserts, removal fact for removes, "needs poll" for updates.
  // most recent overrides.)
  //
  // 2) read the initial set and send added messages.
  //
  // 3) write a sentinel to some field.
  //
  // 4) wait until that sentinel comes up through the oplog.
  //
  // 5) use the cached information (compared to what we already know) to send
  //    messages about things that changed right about then
  //
  // 6) now that we're in the "steady state", process ops more directly

  // XXX NOW: replace idSet/changedFields with simply currently published
  // results, ok??? that should simplify things, and allow the implementation of
  // "replace" (noodles)

  // XXX DOC: map id -> currently published fields
  //          (which of course is also the same as what is tracked in merge box,
  //           ah well)
  var published = new IdMap;

  var selector = LocalCollection._compileSelector(cursorDescription.selector);

  // XXX add mutates its argument, which could get confusing
  var add = function (doc) {
    var id = doc._id;
    delete doc._id;
    published.set(id, doc);
    callbacks.added && callbacks.added(id, doc);
  };

  var remove = function (id) {
    published.remove(id);
    callbacks.removed && callbacks.removed(id);
  };

  // XXX the ordering here is wrong
  var initialCursor = new Cursor(self, cursorDescription);
  initialCursor.forEach(function (initialDoc) {
    add(initialDoc);
  });

  var oplogHandle = self._oplogHandle.onOplogEntry(cursorDescription.collectionName, function (op) {
    var id;
    if (op.op === 'd') {
      // XXX check that ObjectId works here
      id = op.o._id;
      if (published.has(id))
        remove(id);

      // XXX this needs to cancel any in-progress "ID lookup" for the document
    } else if (op.op === 'i') {
      id = op.o._id;
      if (published.has(id))
        throw new Error("insert found for already-existing ID");

      // XXX what if selector yields?  for now it can't but later it could have
      // $where
      if (selector(op.o)) {
        add(op.o);
      }
    } else if (op.op === 'u') {
      id = op.o2._id;

      // Is this a modifier ($set/$unset, which may require us to poll the
      // database to figure out if the whole document matches the selector) or a
      // replacement (in which case we can just directly re-evaluate the
      // selector)?
      var isModifier = _.has(op.o, '$set') || _.has(op.o, '$unset');

      var newDoc;
      if (isModifier) {
        // XXX problem is, the result of this findOne is delivered at a random
        // time, not necessarily synced with other stuff that may be coming down
        // the oplog. also, we should coalesce multiple pings of the same
        // document ("ID queue"). also, we shouldn't read fields that aren't
        // necessary to evaluate selector or to publish.
        newDoc = self.findOne(cursorDescription.collectionName, {_id: id});
      } else {
        newDoc = _.extend({_id: id}, op.o);
      }

      var matchesNow = newDoc && selector(newDoc);
      var matchedBefore = published.has(id);
      if (matchesNow && !matchedBefore) {
        add(newDoc);
      } else if (matchedBefore && !matchesNow) {
        remove(id);
      } else if (matchesNow) {
        var oldDoc = published.get(id);
        if (!oldDoc)
          throw Error("thought that " + id + " was there!");
        delete newDoc._id;
        published.set(id, newDoc);
        if (callbacks.changed) {
          var changed = LocalCollection._makeChangedFields(newDoc, oldDoc);
          if (!_.isEmpty(changed)) {
            callbacks.changed(id, changed);
          }
        }
      }
    } else {
      console.log("SURPRISING FOR NOW OPERATION (eg drop collection)", op);
    }
  });

  // XXX ordering w.r.t. everything else?
  var listenersHandle = listenAll(
    cursorDescription, function (notification, complete) {
      // If we're not in a write fence, we don't have to do anything. That's
      // because
      var fence = DDPServer._CurrentWriteFence.get();
      if (!fence) {
        complete();
        return;
      }
      var writeId = nextWriteId++;
      var write = fence.beginWrite();
      outstandingWrites.push({writeId: writeId, write: write});

      // Use direct write to Node Mongo driver so we don't end up with recursive
      // fence stuff. Need to disable 'safe' because we aren't providing a
      // callback.
      var writeCollection = self._getCollection(WRITE_COLLECTION);
      writeCollection.update({_id: myServerId}, {$set: {write: writeId}},
                             {upsert: true, safe: false});
      complete();
    }
  );

  var observeHandle = {
    stop: function () {
      oplogHandle.stop();
    }
  };
  return observeHandle;
};

//////////// Public API //////////

// The write methods block until the database has confirmed the write (it may
// not be replicated or stable on disk, but one server has confirmed it) if no
// callback is provided. If a callback is provided, then they call the callback
// when the write is confirmed. They return nothing on success, and raise an
// exception on failure.
//
// After making a write (with insert, update, remove), observers are
// notified asynchronously. If you want to receive a callback once all
// of the observer notifications have landed for your write, do the
// writes inside a write fence (set DDPServer._CurrentWriteFence to a new
// _WriteFence, and then set a callback on the write fence.)
//
// Since our execution environment is single-threaded, this is
// well-defined -- a write "has been made" if it's returned, and an
// observer "has been notified" if its callback has returned.

var writeCallback = function (write, refresh, callback) {
  return function (err, result) {
    if (! err) {
      // XXX We don't have to run this on error, right?
      refresh();
    }
    write.committed();
    if (callback)
      callback(err, result);
    else if (err)
      throw err;
  };
};

var bindEnvironmentForWrite = function (callback) {
  return Meteor.bindEnvironment(callback, function (err) {
    Meteor._debug("Error in Mongo write:", err.stack);
  });
};

MongoConnection.prototype._insert = function (collection_name, document,
                                              callback) {
  var self = this;
  if (collection_name === "___meteor_failure_test_collection") {
    var e = new Error("Failure test");
    e.expected = true;
    if (callback)
      return callback(e);
    else
      throw e;
  }

  var write = self._maybeBeginWrite();
  var refresh = function () {
    Meteor.refresh({ collection: collection_name, id: document._id });
  };
  callback = bindEnvironmentForWrite(writeCallback(write, refresh, callback));
  try {
    var collection = self._getCollection(collection_name);
    collection.insert(replaceTypes(document, replaceMeteorAtomWithMongo),
                      {safe: true}, callback);
  } catch (e) {
    write.committed();
    throw e;
  }
};

// Cause queries that may be affected by the selector to poll in this write
// fence.
MongoConnection.prototype._refresh = function (collectionName, selector) {
  var self = this;
  var refreshKey = {collection: collectionName};
  // If we know which documents we're removing, don't poll queries that are
  // specific to other documents. (Note that multiple notifications here should
  // not cause multiple polls, since all our listener is doing is enqueueing a
  // poll.)
  var specificIds = LocalCollection._idsMatchedBySelector(selector);
  if (specificIds) {
    _.each(specificIds, function (id) {
      Meteor.refresh(_.extend({id: id}, refreshKey));
    });
  } else {
    Meteor.refresh(refreshKey);
  }
};

MongoConnection.prototype._remove = function (collection_name, selector,
                                              callback) {
  var self = this;

  if (collection_name === "___meteor_failure_test_collection") {
    var e = new Error("Failure test");
    e.expected = true;
    if (callback)
      return callback(e);
    else
      throw e;
  }

  var write = self._maybeBeginWrite();
  var refresh = function () {
    self._refresh(collection_name, selector);
  };
  callback = bindEnvironmentForWrite(writeCallback(write, refresh, callback));

  try {
    var collection = self._getCollection(collection_name);
    collection.remove(replaceTypes(selector, replaceMeteorAtomWithMongo),
                      {safe: true}, callback);
  } catch (e) {
    write.committed();
    throw e;
  }
};

MongoConnection.prototype._update = function (collection_name, selector, mod,
                                              options, callback) {
  var self = this;

  if (! callback && options instanceof Function) {
    callback = options;
    options = null;
  }

  if (collection_name === "___meteor_failure_test_collection") {
    var e = new Error("Failure test");
    e.expected = true;
    if (callback)
      return callback(e);
    else
      throw e;
  }

  // explicit safety check. null and undefined can crash the mongo
  // driver. Although the node driver and minimongo do 'support'
  // non-object modifier in that they don't crash, they are not
  // meaningful operations and do not do anything. Defensively throw an
  // error here.
  if (!mod || typeof mod !== 'object')
    throw new Error("Invalid modifier. Modifier must be an object.");

  if (!options) options = {};

  var write = self._maybeBeginWrite();
  var refresh = function () {
    self._refresh(collection_name, selector);
  };
  callback = writeCallback(write, refresh, callback);
  try {
    var collection = self._getCollection(collection_name);
    var mongoOpts = {safe: true};
    // explictly enumerate options that minimongo supports
    if (options.upsert) mongoOpts.upsert = true;
    if (options.multi) mongoOpts.multi = true;

    var mongoSelector = replaceTypes(selector, replaceMeteorAtomWithMongo);
    var mongoMod = replaceTypes(mod, replaceMeteorAtomWithMongo);

    var isModify = isModificationMod(mongoMod);
    var knownId = (isModify ? selector._id : mod._id);

    if (options.upsert && (! knownId) && options.insertedId) {
      // XXX In future we could do a real upsert for the mongo id generation
      // case, if the the node mongo driver gives us back the id of the upserted
      // doc (which our current version does not).
      simulateUpsertWithInsertedId(
        collection, mongoSelector, mongoMod,
        isModify, options,
        // This callback does not need to be bindEnvironment'ed because
        // simulateUpsertWithInsertedId() wraps it and then passes it through
        // bindEnvironmentForWrite.
        function (err, result) {
          // If we got here via a upsert() call, then options._returnObject will
          // be set and we should return the whole object. Otherwise, we should
          // just return the number of affected docs to match the mongo API.
          if (result && ! options._returnObject)
            callback(err, result.numberAffected);
          else
            callback(err, result);
        }
      );
    } else {
      collection.update(
        mongoSelector, mongoMod, mongoOpts,
        bindEnvironmentForWrite(function (err, result, extra) {
          if (! err) {
            if (result && options._returnObject) {
              result = { numberAffected: result };
              // If this was an upsert() call, and we ended up
              // inserting a new doc and we know its id, then
              // return that id as well.
              if (options.upsert && knownId &&
                  ! extra.updatedExisting)
                result.insertedId = knownId;
            }
          }
          callback(err, result);
        }));
    }
  } catch (e) {
    write.committed();
    throw e;
  }
};

var isModificationMod = function (mod) {
  for (var k in mod)
    if (k.substr(0, 1) === '$')
      return true;
  return false;
};

var NUM_OPTIMISTIC_TRIES = 3;

// exposed for testing
MongoConnection._isCannotChangeIdError = function (err) {
  // either of these checks should work, but just to be safe...
  return (err.code === 13596 ||
          err.err.indexOf("cannot change _id of a document") === 0);
};

var simulateUpsertWithInsertedId = function (collection, selector, mod,
                                             isModify, options, callback) {
  // STRATEGY:  First try doing a plain update.  If it affected 0 documents,
  // then without affecting the database, we know we should probably do an
  // insert.  We then do a *conditional* insert that will fail in the case
  // of a race condition.  This conditional insert is actually an
  // upsert-replace with an _id, which will never successfully update an
  // existing document.  If this upsert fails with an error saying it
  // couldn't change an existing _id, then we know an intervening write has
  // caused the query to match something.  We go back to step one and repeat.
  // Like all "optimistic write" schemes, we rely on the fact that it's
  // unlikely our writes will continue to be interfered with under normal
  // circumstances (though sufficiently heavy contention with writers
  // disagreeing on the existence of an object will cause writes to fail
  // in theory).

  var newDoc;
  // Run this code up front so that it fails fast if someone uses
  // a Mongo update operator we don't support.
  if (isModify) {
    // We've already run replaceTypes/replaceMeteorAtomWithMongo on
    // selector and mod.  We assume it doesn't matter, as far as
    // the behavior of modifiers is concerned, whether `_modify`
    // is run on EJSON or on mongo-converted EJSON.
    var selectorDoc = LocalCollection._removeDollarOperators(selector);
    LocalCollection._modify(selectorDoc, mod, true);
    newDoc = selectorDoc;
  } else {
    newDoc = mod;
  }

  var insertedId = options.insertedId; // must exist
  var mongoOptsForUpdate = {
    safe: true,
    multi: options.multi
  };
  var mongoOptsForInsert = {
    safe: true,
    upsert: true
  };

  var tries = NUM_OPTIMISTIC_TRIES;

  var doUpdate = function () {
    tries--;
    if (! tries) {
      callback(new Error("Upsert failed after " + NUM_OPTIMISTIC_TRIES + " tries."));
    } else {
      collection.update(selector, mod, mongoOptsForUpdate,
                        bindEnvironmentForWrite(function (err, result) {
                          if (err)
                            callback(err);
                          else if (result)
                            callback(null, {
                              numberAffected: result
                            });
                          else
                            doConditionalInsert();
                        }));
    }
  };

  var doConditionalInsert = function () {
    var replacementWithId = _.extend(
      replaceTypes({_id: insertedId}, replaceMeteorAtomWithMongo),
      newDoc);
    collection.update(selector, replacementWithId, mongoOptsForInsert,
                      bindEnvironmentForWrite(function (err, result) {
                        if (err) {
                          // figure out if this is a
                          // "cannot change _id of document" error, and
                          // if so, try doUpdate() again, up to 3 times.
                          if (MongoConnection._isCannotChangeIdError(err)) {
                            doUpdate();
                          } else {
                            callback(err);
                          }
                        } else {
                          callback(null, {
                            numberAffected: result,
                            insertedId: insertedId
                          });
                        }
                      }));
  };

  doUpdate();
};

_.each(["insert", "update", "remove"], function (method) {
  MongoConnection.prototype[method] = function (/* arguments */) {
    var self = this;
    return Meteor._wrapAsync(self["_" + method]).apply(self, arguments);
  };
});

// XXX MongoConnection.upsert() does not return the id of the inserted document
// unless you set it explicitly in the selector or modifier (as a replacement
// doc).
MongoConnection.prototype.upsert = function (collectionName, selector, mod,
                                             options, callback) {
  var self = this;
  if (typeof options === "function" && ! callback) {
    callback = options;
    options = {};
  }

  return self.update(collectionName, selector, mod,
                     _.extend({}, options, {
                       upsert: true,
                       _returnObject: true
                     }), callback);
};

MongoConnection.prototype.find = function (collectionName, selector, options) {
  var self = this;

  if (arguments.length === 1)
    selector = {};

  return new Cursor(
    self, new CursorDescription(collectionName, selector, options));
};

MongoConnection.prototype.findOne = function (collection_name, selector,
                                              options) {
  var self = this;
  if (arguments.length === 1)
    selector = {};

  options = options || {};
  options.limit = 1;
  return self.find(collection_name, selector, options).fetch()[0];
};

// We'll actually design an index API later. For now, we just pass through to
// Mongo's, but make it synchronous.
MongoConnection.prototype._ensureIndex = function (collectionName, index,
                                                   options) {
  var self = this;
  options = _.extend({safe: true}, options);

  // We expect this function to be called at startup, not from within a method,
  // so we don't interact with the write fence.
  var collection = self._getCollection(collectionName);
  var future = new Future;
  var indexName = collection.ensureIndex(index, options, future.resolver());
  future.wait();
};
MongoConnection.prototype._dropIndex = function (collectionName, index) {
  var self = this;

  // This function is only used by test code, not within a method, so we don't
  // interact with the write fence.
  var collection = self._getCollection(collectionName);
  var future = new Future;
  var indexName = collection.dropIndex(index, future.resolver());
  future.wait();
};

// CURSORS

// There are several classes which relate to cursors:
//
// CursorDescription represents the arguments used to construct a cursor:
// collectionName, selector, and (find) options.  Because it is used as a key
// for cursor de-dup, everything in it should either be JSON-stringifiable or
// not affect observeChanges output (eg, options.transform functions are not
// stringifiable but do not affect observeChanges).
//
// SynchronousCursor is a wrapper around a MongoDB cursor
// which includes fully-synchronous versions of forEach, etc.
//
// Cursor is the cursor object returned from find(), which implements the
// documented Meteor.Collection cursor API.  It wraps a CursorDescription and a
// SynchronousCursor (lazily: it doesn't contact Mongo until you call a method
// like fetch or forEach on it).
//
// ObserveHandle is the "observe handle" returned from observeChanges. It has a
// reference to a LiveResultsSet.
//
// LiveResultsSet caches the results of a query and reruns it when necessary.
// It is hooked up to one or more ObserveHandles; a single LiveResultsSet
// can drive multiple sets of observation callbacks if they are for the
// same query.

var CursorDescription = function (collectionName, selector, options) {
  var self = this;
  self.collectionName = collectionName;
  self.selector = Meteor.Collection._rewriteSelector(selector);
  self.options = options || {};
};

var Cursor = function (mongo, cursorDescription) {
  var self = this;

  self._mongo = mongo;
  self._cursorDescription = cursorDescription;
  self._synchronousCursor = null;
};

_.each(['forEach', 'map', 'rewind', 'fetch', 'count'], function (method) {
  Cursor.prototype[method] = function () {
    var self = this;

    // You can only observe a tailable cursor.
    if (self._cursorDescription.options.tailable)
      throw new Error("Cannot call " + method + " on a tailable cursor");

    if (!self._synchronousCursor) {
      self._synchronousCursor = self._mongo._createSynchronousCursor(
        self._cursorDescription, {
          // Make sure that the "self" argument to forEach/map callbacks is the
          // Cursor, not the SynchronousCursor.
          selfForIteration: self,
          useTransform: true
        });
    }

    return self._synchronousCursor[method].apply(
      self._synchronousCursor, arguments);
  };
});

Cursor.prototype.getTransform = function () {
  var self = this;
  return self._cursorDescription.options.transform;
};

// When you call Meteor.publish() with a function that returns a Cursor, we need
// to transmute it into the equivalent subscription.  This is the function that
// does that.

Cursor.prototype._publishCursor = function (sub) {
  var self = this;
  var collection = self._cursorDescription.collectionName;
  return Meteor.Collection._publishCursor(self, sub, collection);
};

// Used to guarantee that publish functions return at most one cursor per
// collection. Private, because we might later have cursors that include
// documents from multiple collections somehow.
Cursor.prototype._getCollectionName = function () {
  var self = this;
  return self._cursorDescription.collectionName;
}

Cursor.prototype.observe = function (callbacks) {
  var self = this;
  return LocalCollection._observeFromObserveChanges(self, callbacks);
};

Cursor.prototype.observeChanges = function (callbacks) {
  var self = this;
  var ordered = LocalCollection._isOrderedChanges(callbacks);
  return self._mongo._observeChanges(
    self._cursorDescription, ordered, callbacks);
};

MongoConnection.prototype._createSynchronousCursor = function(
    cursorDescription, options) {
  var self = this;
  options = _.pick(options || {}, 'selfForIteration', 'useTransform');

  var collection = self._getCollection(cursorDescription.collectionName);
  var cursorOptions = cursorDescription.options;
  var mongoOptions = {
    sort: cursorOptions.sort,
    limit: cursorOptions.limit,
    skip: cursorOptions.skip
  };

  // Do we want a tailable cursor (which only works on capped collections)?
  if (cursorOptions.tailable) {
    // We want a tailable cursor...
    mongoOptions.tailable = true;
    // ... and for the server to wait a bit if any getMore has no data (rather
    // than making us put the relevant sleeps in the client)...
    mongoOptions.awaitdata = true;
    // ... and to keep querying the server indefinitely rather than just 5 times
    // if there's no more data.
    mongoOptions.numberOfRetries = -1;
  }

  var dbCursor = collection.find(
    replaceTypes(cursorDescription.selector, replaceMeteorAtomWithMongo),
    cursorOptions.fields, mongoOptions);

  return new SynchronousCursor(dbCursor, cursorDescription, options);
};

var SynchronousCursor = function (dbCursor, cursorDescription, options) {
  var self = this;
  options = _.pick(options || {}, 'selfForIteration', 'useTransform');

  self._dbCursor = dbCursor;
  self._cursorDescription = cursorDescription;
  // The "self" argument passed to forEach/map callbacks. If we're wrapped
  // inside a user-visible Cursor, we want to provide the outer cursor!
  self._selfForIteration = options.selfForIteration || self;
  if (options.useTransform && cursorDescription.options.transform) {
    self._transform = Deps._makeNonreactive(
      cursorDescription.options.transform
    );
  } else {
    self._transform = null;
  }

  // Need to specify that the callback is the first argument to nextObject,
  // since otherwise when we try to call it with no args the driver will
  // interpret "undefined" first arg as an options hash and crash.
  self._synchronousNextObject = Future.wrap(
    dbCursor.nextObject.bind(dbCursor), 0);
  self._synchronousCount = Future.wrap(dbCursor.count.bind(dbCursor));
  self._visitedIds = {};
};

_.extend(SynchronousCursor.prototype, {
  _nextObject: function () {
    var self = this;

    while (true) {
      var doc = self._synchronousNextObject().wait();

      if (!doc) return null;
      doc = replaceTypes(doc, replaceMongoAtomWithMeteor);

      if (!self._cursorDescription.options.tailable && _.has(doc, '_id')) {
        // Did Mongo give us duplicate documents in the same cursor? If so,
        // ignore this one. (Do this before the transform, since transform might
        // return some unrelated value.) We don't do this for tailable cursors,
        // because we want to maintain O(1) memory usage. And if there isn't _id
        // for some reason (maybe it's the oplog), then we don't do this either.
        // (Be careful to do this for falsey but existing _id, though.)
        var strId = LocalCollection._idStringify(doc._id);
        if (self._visitedIds[strId]) continue;
        self._visitedIds[strId] = true;
      }

      if (self._transform)
        doc = self._transform(doc);

      return doc;
    }
  },

  forEach: function (callback, thisArg) {
    var self = this;

    // We implement the loop ourself instead of using self._dbCursor.each,
    // because "each" will call its callback outside of a fiber which makes it
    // much more complex to make this function synchronous.
    var index = 0;
    while (true) {
      var doc = self._nextObject();
      if (!doc) return;
      callback.call(thisArg, doc, index++, self._selfForIteration);
    }
  },

  // XXX Allow overlapping callback executions if callback yields.
  map: function (callback, thisArg) {
    var self = this;
    var res = [];
    self.forEach(function (doc, index) {
      res.push(callback.call(thisArg, doc, index, self._selfForIteration));
    });
    return res;
  },

  rewind: function () {
    var self = this;

    // known to be synchronous
    self._dbCursor.rewind();

    self._visitedIds = {};
  },

  // Mostly usable for tailable cursors.
  close: function () {
    var self = this;

    self._dbCursor.close();
  },

  fetch: function () {
    var self = this;
    return self.map(_.identity);
  },

  count: function () {
    var self = this;
    return self._synchronousCount().wait();
  },

  // This method is NOT wrapped in Cursor.
  getRawObjects: function (ordered) {
    var self = this;
    if (ordered) {
      return self.fetch();
    } else {
      var results = {};
      self.forEach(function (doc) {
        results[doc._id] = doc;
      });
      return results;
    }
  }
});

MongoConnection.prototype.tail = function (cursorDescription, docCallback) {
  var self = this;
  if (!cursorDescription.options.tailable)
    throw new Error("Can only tail a tailable cursor");

  var cursor = self._createSynchronousCursor(cursorDescription);

  var stopped = false;
  var lastTS = undefined;
  Meteor.defer(function () {
    while (true) {
      if (stopped)
        return;
      try {
        var doc = cursor._nextObject();
      } catch (err) {
        // There's no good way to figure out if this was actually an error
        // from Mongo. Ah well. But either way, we need to retry the cursor
        // (unless the failure was because the observe got stopped).
        doc = null;
      }
      if (stopped)
        return;
      if (doc) {
        // If a tailable cursor contains a "ts" field, use it to recreate the
        // cursor on error. ("ts" is a standard that Mongo uses internally for
        // the oplog, and there's a special flag that lets you do binary search
        // on it instead of needing to use an index.)
        lastTS = doc.ts;
        docCallback(doc);
      } else {
        var newSelector = _.clone(cursorDescription.selector);
        if (lastTS) {
          newSelector.ts = {$gt: lastTS};
        }
        // XXX maybe set replay flag
        cursor = self._createSynchronousCursor(new CursorDescription(
          cursorDescription.collectionName,
          newSelector,
          cursorDescription.options));
      }
    }
  });

  return {
    stop: function () {
      stopped = true;
      cursor.close();
    }
  };
};

var nextObserveHandleId = 1;
var ObserveHandle = function (liveResultsSet, callbacks) {
  var self = this;
  self._liveResultsSet = liveResultsSet;
  self._added = callbacks.added;
  self._addedBefore = callbacks.addedBefore;
  self._changed = callbacks.changed;
  self._removed = callbacks.removed;
  self._moved = callbacks.moved;
  self._movedBefore = callbacks.movedBefore;
  self._observeHandleId = nextObserveHandleId++;
};
ObserveHandle.prototype.stop = function () {
  var self = this;
  self._liveResultsSet._removeObserveHandle(self);
  self._liveResultsSet = null;
};

MongoConnection.prototype._observeChanges = function (
    cursorDescription, ordered, callbacks) {
  var self = this;

  if (cursorDescription.options.tailable) {
    return self._observeChangesTailable(cursorDescription, ordered, callbacks);
  }

  // XXX maybe this should actually use deduping too?
  if (self._oplogHandle && !ordered
      && cursorSupportedByOplogTailing(cursorDescription)) {
    return self._observeChangesWithOplog(cursorDescription, callbacks);
  }

  var observeKey = JSON.stringify(
    _.extend({ordered: ordered}, cursorDescription));

  var liveResultsSet;
  var observeHandle;
  var newlyCreated = false;

  // Find a matching LiveResultsSet, or create a new one. This next block is
  // guaranteed to not yield (and it doesn't call anything that can observe a
  // new query), so no other calls to this function can interleave with it.
  Meteor._noYieldsAllowed(function () {
    if (_.has(self._liveResultsSets, observeKey)) {
      liveResultsSet = self._liveResultsSets[observeKey];
    } else {
      // Create a new LiveResultsSet. It is created "locked": no polling can
      // take place.
      liveResultsSet = new LiveResultsSet(
        cursorDescription,
        self,
        ordered,
        function () {
          delete self._liveResultsSets[observeKey];
        },
        callbacks._testOnlyPollCallback);
      self._liveResultsSets[observeKey] = liveResultsSet;
      newlyCreated = true;
    }
    observeHandle = new ObserveHandle(liveResultsSet, callbacks);
  });

  if (newlyCreated) {
    // This is the first ObserveHandle on this LiveResultsSet.  Add it and run
    // the initial synchronous poll (which may yield).
    liveResultsSet._addFirstObserveHandle(observeHandle);
  } else {
    // Not the first ObserveHandle. Add it to the LiveResultsSet. This call
    // yields until we're not in the middle of a poll, and its invocation of the
    // initial 'added' callbacks may yield as well. It blocks until the 'added'
    // callbacks have fired.
    liveResultsSet._addObserveHandleAndSendInitialAdds(observeHandle);
  }

  return observeHandle;
};

// Listen for the invalidation messages that will trigger us to poll the
// database for changes. If this selector specifies specific IDs, specify them
// here, so that updates to different specific IDs don't cause us to poll.
// listenCallback is the same kind of (notification, complete) callback passed
// to InvalidationCrossbar.listen.
var listenAll = function (cursorDescription, listenCallback) {
  var listeners = [];
  var listenOnTrigger = function (trigger) {
    listeners.push(DDPServer._InvalidationCrossbar.listen(
      trigger, listenCallback));
  };

  var key = {collection: cursorDescription.collectionName};
  var specificIds = LocalCollection._idsMatchedBySelector(
    cursorDescription.selector);
  if (specificIds) {
    _.each(specificIds, function (id) {
      listenOnTrigger(_.extend({id: id}, key));
    });
  } else {
    listenOnTrigger(key);
  }

  return {
    stop: function () {
      _.each(listeners, function (listener) {
        listener.stop();
      });
    }
  };
};

var LiveResultsSet = function (cursorDescription, mongoHandle, ordered,
                               stopCallback, testOnlyPollCallback) {
  var self = this;

  self._cursorDescription = cursorDescription;
  self._mongoHandle = mongoHandle;
  self._ordered = ordered;
  self._stopCallbacks = [stopCallback];

  // This constructor cannot yield, so we don't create the synchronousCursor yet
  // (since that can yield).
  self._synchronousCursor = null;

  // previous results snapshot.  on each poll cycle, diffs against
  // results drives the callbacks.
  self._results = ordered ? [] : {};

  // The number of _pollMongo calls that have been added to self._taskQueue but
  // have not started running. Used to make sure we never schedule more than one
  // _pollMongo (other than possibly the one that is currently running). It's
  // also used by _suspendPolling to pretend there's a poll scheduled. Usually,
  // it's either 0 (for "no polls scheduled other than maybe one currently
  // running") or 1 (for "a poll scheduled that isn't running yet"), but it can
  // also be 2 if incremented by _suspendPolling.
  self._pollsScheduledButNotStarted = 0;
  // Number of _addObserveHandleAndSendInitialAdds tasks scheduled but not yet
  // running. _removeObserveHandle uses this to know if it's safe to shut down
  // this LiveResultsSet.
  self._addHandleTasksScheduledButNotPerformed = 0;
  self._pendingWrites = []; // people to notify when polling completes

  // Make sure to create a separately throttled function for each LiveResultsSet
  // object.
  self._ensurePollIsScheduled = _.throttle(
    self._unthrottledEnsurePollIsScheduled, 50 /* ms */);

  self._taskQueue = new Meteor._SynchronousQueue();

  var listenersHandle = listenAll(
    cursorDescription, function (notification, complete) {
      // When someone does a transaction that might affect us, schedule a poll
      // of the database. If that transaction happens inside of a write fence,
      // block the fence until we've polled and notified observers.
      var fence = DDPServer._CurrentWriteFence.get();
      if (fence)
        self._pendingWrites.push(fence.beginWrite());
      // Ensure a poll is scheduled... but if we already know that one is,
      // don't hit the throttled _ensurePollIsScheduled function (which might
      // lead to us calling it unnecessarily in 50ms).
      if (self._pollsScheduledButNotStarted === 0)
        self._ensurePollIsScheduled();
      complete();
    }
  );
  self._stopCallbacks.push(function () { listenersHandle.stop(); });

  // Map from handle ID to ObserveHandle.
  self._observeHandles = {};

  self._callbackMultiplexer = {};
  var callbackNames = ['added', 'changed', 'removed'];
  if (self._ordered) {
    callbackNames.push('moved');
    callbackNames.push('addedBefore');
    callbackNames.push('movedBefore');
  }
  _.each(callbackNames, function (callback) {
    var handleCallback = '_' + callback;
    self._callbackMultiplexer[callback] = function () {
      var args = _.toArray(arguments);
      // Because callbacks can yield and _removeObserveHandle() (ie,
      // handle.stop()) doesn't synchronize its actions with _taskQueue,
      // ObserveHandles can disappear from self._observeHandles during this
      // dispatch. Thus, we save a copy of the keys of self._observeHandles
      // before we start to iterate, and we check to see if the handle is still
      // there each time.
      _.each(_.keys(self._observeHandles), function (handleId) {
        var handle = self._observeHandles[handleId];
        if (handle && handle[handleCallback])
          handle[handleCallback].apply(null, EJSON.clone(args));
      });
    };
  });

  // every once and a while, poll even if we don't think we're dirty, for
  // eventual consistency with database writes from outside the Meteor
  // universe.
  //
  // For testing, there's an undocumented callback argument to observeChanges
  // which disables time-based polling and gets called at the beginning of each
  // poll.
  if (testOnlyPollCallback) {
    self._testOnlyPollCallback = testOnlyPollCallback;
  } else {
    var intervalHandle = Meteor.setInterval(
      _.bind(self._ensurePollIsScheduled, self), 10 * 1000);
    self._stopCallbacks.push(function () {
      Meteor.clearInterval(intervalHandle);
    });
  }

  Package.facts && Package.facts.Facts.incrementServerFact(
    "mongo-livedata", "live-results-sets", 1);
};

_.extend(LiveResultsSet.prototype, {
  _addFirstObserveHandle: function (handle) {
    var self = this;
    if (! _.isEmpty(self._observeHandles))
      throw new Error("Not the first observe handle!");
    if (! _.isEmpty(self._results))
      throw new Error("Call _addFirstObserveHandle before polling!");

    self._observeHandles[handle._observeHandleId] = handle;
    Package.facts && Package.facts.Facts.incrementServerFact(
      "mongo-livedata", "observe-handles", 1);

    // Run the first _poll() cycle synchronously (delivering results to the
    // first ObserveHandle).
    ++self._pollsScheduledButNotStarted;
    self._taskQueue.runTask(function () {
      self._pollMongo();
    });
  },

  // This is always called through _.throttle.
  _unthrottledEnsurePollIsScheduled: function () {
    var self = this;
    if (self._pollsScheduledButNotStarted > 0)
      return;
    ++self._pollsScheduledButNotStarted;
    self._taskQueue.queueTask(function () {
      self._pollMongo();
    });
  },

  // test-only interface for controlling polling.
  //
  // _suspendPolling blocks until any currently running and scheduled polls are
  // done, and prevents any further polls from being scheduled. (new
  // ObserveHandles can be added and receive their initial added callbacks,
  // though.)
  //
  // _resumePolling immediately polls, and allows further polls to occur.
  _suspendPolling: function() {
    var self = this;
    // Pretend that there's another poll scheduled (which will prevent
    // _ensurePollIsScheduled from queueing any more polls).
    ++self._pollsScheduledButNotStarted;
    // Now block until all currently running or scheduled polls are done.
    self._taskQueue.runTask(function() {});

    // Confirm that there is only one "poll" (the fake one we're pretending to
    // have) scheduled.
    if (self._pollsScheduledButNotStarted !== 1)
      throw new Error("_pollsScheduledButNotStarted is " +
                      self._pollsScheduledButNotStarted);
  },
  _resumePolling: function() {
    var self = this;
    // We should be in the same state as in the end of _suspendPolling.
    if (self._pollsScheduledButNotStarted !== 1)
      throw new Error("_pollsScheduledButNotStarted is " +
                      self._pollsScheduledButNotStarted);
    // Run a poll synchronously (which will counteract the
    // ++_pollsScheduledButNotStarted from _suspendPolling).
    self._taskQueue.runTask(function () {
      self._pollMongo();
    });
  },

  _pollMongo: function () {
    var self = this;
    --self._pollsScheduledButNotStarted;

    self._testOnlyPollCallback && self._testOnlyPollCallback();

    // Save the list of pending writes which this round will commit.
    var writesForCycle = self._pendingWrites;
    self._pendingWrites = [];

    // Get the new query results. (These calls can yield.)
    if (self._synchronousCursor) {
      self._synchronousCursor.rewind();
    } else {
      self._synchronousCursor = self._mongoHandle._createSynchronousCursor(
        self._cursorDescription);
    }
    var newResults = self._synchronousCursor.getRawObjects(self._ordered);
    var oldResults = self._results;

    // Run diffs. (This can yield too.)
    if (!_.isEmpty(self._observeHandles)) {
      LocalCollection._diffQueryChanges(
        self._ordered, oldResults, newResults, self._callbackMultiplexer);
    }

    // Replace self._results atomically.
    self._results = newResults;

    // Mark all the writes which existed before this call as commmitted. (If new
    // writes have shown up in the meantime, there'll already be another
    // _pollMongo task scheduled.)
    _.each(writesForCycle, function (w) {w.committed();});
  },

  // Adds the observe handle to this set and sends its initial added
  // callbacks. Meteor._SynchronousQueue guarantees that this won't interleave
  // with a call to _pollMongo or another call to this function.
  _addObserveHandleAndSendInitialAdds: function (handle) {
    var self = this;

    // Check this before calling runTask (even though runTask does the same
    // check) so that we don't leak a LiveResultsSet by incrementing
    // _addHandleTasksScheduledButNotPerformed and never decrementing it.
    if (!self._taskQueue.safeToRunTask())
      throw new Error(
        "Can't call observe() from an observe callback on the same query");

    // Keep track of how many of these tasks are on the queue, so that
    // _removeObserveHandle knows if it's safe to GC.
    ++self._addHandleTasksScheduledButNotPerformed;

    self._taskQueue.runTask(function () {
      if (!self._observeHandles)
        throw new Error("Can't add observe handle to stopped LiveResultsSet");

      if (_.has(self._observeHandles, handle._observeHandleId))
        throw new Error("Duplicate observe handle ID");
      self._observeHandles[handle._observeHandleId] = handle;
      --self._addHandleTasksScheduledButNotPerformed;
      Package.facts && Package.facts.Facts.incrementServerFact(
        "mongo-livedata", "observe-handles", 1);

      // Send initial adds.
      if (handle._added || handle._addedBefore) {
        _.each(self._results, function (doc, i) {
          var fields = EJSON.clone(doc);
          delete fields._id;
          if (self._ordered) {
            handle._added && handle._added(doc._id, fields);
            handle._addedBefore && handle._addedBefore(doc._id, fields, null);
          } else {
            handle._added(doc._id, fields);
          }
        });
      }
    });
  },

  // Remove an observe handle. If it was the last observe handle, call all the
  // stop callbacks; you cannot add any more observe handles after this.
  //
  // This is not synchronized with polls and handle additions: this means that
  // you can safely call it from within an observe callback.
  _removeObserveHandle: function (handle) {
    var self = this;

    if (!_.has(self._observeHandles, handle._observeHandleId))
      throw new Error("Unknown observe handle ID " + handle._observeHandleId);
    delete self._observeHandles[handle._observeHandleId];
    Package.facts && Package.facts.Facts.incrementServerFact(
      "mongo-livedata", "observe-handles", -1);

    if (_.isEmpty(self._observeHandles) &&
        self._addHandleTasksScheduledButNotPerformed === 0) {
      // The last observe handle was stopped; call our stop callbacks, which:
      //  - removes us from the MongoConnection's _liveResultsSets map
      //  - stops the poll timer
      //  - removes us from the invalidation crossbar
      _.each(self._stopCallbacks, function (c) { c(); });
      Package.facts && Package.facts.Facts.incrementServerFact(
        "mongo-livedata", "live-results-sets", -1);
      // This will cause future _addObserveHandleAndSendInitialAdds calls to
      // throw.
      self._observeHandles = null;
    }
  }
});

// observeChanges for tailable cursors on capped collections.
//
// Some differences from normal cursors:
//   - Will never produce anything other than 'added' or 'addedBefore'. If you
//     do update a document that has already been produced, this will not notice
//     it.
//   - If you disconnect and reconnect from Mongo, it will essentially restart
//     the query, which will lead to duplicate results. This is pretty bad,
//     but if you include a field called 'ts' which is inserted as
//     new MongoInternals.MongoTimestamp(0, 0) (which is initialized to the
//     current Mongo-style timestamp), we'll be able to find the place to
//     restart properly. (This field is specifically understood by Mongo with an
//     optimization which allows it to find the right place to start without
//     an index on ts. It's how the oplog works.)
//   - No callbacks are triggered synchronously with the call (there's no
//     differentiation between "initial data" and "later changes"; everything
//     that matches the query gets sent asynchronously).
//   - De-duplication is not implemented.
//   - Does not yet interact with the write fence. Probably, this should work by
//     ignoring removes (which don't work on capped collections) and updates
//     (which don't affect tailable cursors), and just keeping track of the ID
//     of the inserted object, and closing the write fence once you get to that
//     ID (or timestamp?).  This doesn't work well if the document doesn't match
//     the query, though.  On the other hand, the write fence can close
//     immediately if it does not match the query. So if we trust minimongo
//     enough to accurately evaluate the query against the write fence, we
//     should be able to do this...  Of course, minimongo doesn't even support
//     Mongo Timestamps yet.
MongoConnection.prototype._observeChangesTailable = function (
    cursorDescription, ordered, callbacks) {
  var self = this;

  // Tailable cursors only ever call added/addedBefore callbacks, so it's an
  // error if you didn't provide them.
  if ((ordered && !callbacks.addedBefore) ||
      (!ordered && !callbacks.added)) {
    throw new Error("Can't observe an " + (ordered ? "ordered" : "unordered")
                    + " tailable cursor without a "
                    + (ordered ? "addedBefore" : "added") + " callback");
  }

  return self.tail(cursorDescription, function (doc) {
    var id = doc._id;
    delete doc._id;
    // The ts is an implementation detail. Hide it.
    delete doc.ts;
    if (ordered) {
      callbacks.addedBefore(id, doc, null);
    } else {
      callbacks.added(id, doc);
    }
  });
};

// Does our oplog tailing code support this cursor? For now, we are being very
// conservative and allowing only simple queries with simple options.
var cursorSupportedByOplogTailing = function (cursorDescription) {
  // First, check the options.
  var options = cursorDescription.options;

  // We don't yet implement field filtering for oplog tailing (just because it's
  // not implemented, not because there's a deep problem with implementing it).
  if (options.fields) return false;

  // This option (which are mostly used for sorted cursors) require us to figure
  // out where a given document fits in an order to know if it's included or
  // not, and we don't track that information when doing oplog tailing.
  if (options.limit || options.skip) return false;

  // For now, we're just dealing with equality queries: no $operators, regexps,
  // or $and/$or/$where/etc clauses. We can expand the scope of what we're
  // comfortable processing later. ($where will get pretty scary since it will
  // allow selector processing to yield!)
  return _.all(cursorDescription.selector, function (value, field) {
    // No logical operators like $and.
    if (field.substr(0, 1) === '$')
      return false;
    // We only allow scalars, not sub-documents or $operators or RegExp.
    // XXX Date would be easy too, though I doubt anyone is doing equality
    // lookups on dates
    return typeof value === "string" ||
      typeof value === "number" ||
      typeof value === "boolean" ||
      value === null ||
      value instanceof Meteor.Collection.ObjectID;
  });
};

// XXX We probably need to find a better way to expose this. Right now
// it's only used by tests, but in fact you need it in normal
// operation to interact with capped collections (eg, Galaxy uses it).
MongoInternals.MongoTimestamp = MongoDB.Timestamp;

MongoInternals.Connection = MongoConnection;
MongoInternals.NpmModule = MongoDB;

MongoTest = {
  cursorSupportedByOplogTailing: cursorSupportedByOplogTailing
};


set -e
trap "echo Failed to fetch binary dependencies." EXIT

if [ -z "$DATA_DIR" ]; then
    echo "Please set DATA_DIR to a writeable directory."
    exit 1
fi

cd `dirname $0`

# Duplicated from scripts/admin/launch-meteor, except that we hardcode the path
# to sysctl. (When satellite spawns ultraworld, ultraworld doesn't have a PATH
# and so can't find sysctl itself.)
UNAME=$(uname)
if [ "$UNAME" != "Linux" -a "$UNAME" != "Darwin" ] ; then
    echo "Sorry, this OS is not supported yet." 1>&2
    exit 1
fi

# If you change this, also change host() in tools/archinfo.js
if [ "$UNAME" = "Darwin" ] ; then
    if [ "i386" != "$(uname -p)" -o "1" != "$(/usr/sbin/sysctl -n hw.cpu64bit_capable 2>/dev/null || echo 0)" ] ; then
        # Can't just test uname -m = x86_64, because Snow Leopard can
        # return other values.
        echo "Only 64-bit Intel processors are supported at this time." 1>&2
        exit 1
    fi
    ARCH="x86_64"
elif [ "$UNAME" = "Linux" ] ; then
    ARCH="$(uname -m)"
    if [ "$ARCH" != "i686" -a "$ARCH" != "x86_64" ] ; then
        echo "Unsupported architecture: $ARCH" 1>&2
        echo "Meteor only supports i686 and x86_64 for now." 1>&2
        exit 1
    fi
fi
PLATFORM="${UNAME}_${ARCH}"

# XXX don't hardcode linux :)
TARBALL="dev_bundle_${PLATFORM}_##BUNDLE_VERSION##.tar.gz"
BUNDLE_TMPDIR="$DATA_DIR/dependencies.fetch"

rm -rf "$BUNDLE_TMPDIR"
mkdir "$BUNDLE_TMPDIR"

# Cache dev bundles in /tmp.
# XXX something more secure is needed in production
CACHED_TARBALL="/tmp/$TARBALL"
if [ ! -r "$CACHED_TARBALL" ]; then
    # Duplicated from 'meteor' script in root of repository
  TEMP_TARBALL="${CACHED_TARBALL}.tmp.${RANDOM}"
  curl -s "https://d3sqy0vbqsdhku.cloudfront.net/$TARBALL" >"$TEMP_TARBALL"
  mv "$TEMP_TARBALL" "$CACHED_TARBALL"
fi
tar -xzf "$CACHED_TARBALL" -C "$BUNDLE_TMPDIR"

# Delete old dev bundle and rename the new one on top of it.
# XXX probably we can just trust that dependencies from last time are good
DEPS_DIR="$DATA_DIR/dependencies"
rm -rf "$DEPS_DIR"
mv "$BUNDLE_TMPDIR" "$DEPS_DIR"

trap - EXIT
set +e

# If there are global node_modules in the bundle, remove them, since
# they override NODE_PATH. Why are these present? Well, 'meteor
# bundle' historically embeds them to give you a self-contained
# bundle. But that never worked very well, because you'd get the
# version for the arch you built on, and you'd have to manually
# rebuild the binary modules (node-fibers) on the target system. It is
# not ideal to have the bundle modify itself (much better for it to be
# immutable) but it'll do for now.
rm -rf node_modules 2>/dev/null || true

export NODE_PATH="$DEPS_DIR/lib/node_modules"
exec "$DEPS_DIR/bin/node" ##RUN_FILE## ##IMAGE## "$@"


////////// Server only logic //////////

Meteor.methods({
  start_new_game: function () {
    // create a new game w/ fresh board
    var game_id = Games.insert({board: new_board(),
                                clock: 120});

    // move everyone who is ready in the lobby to the game
    Players.update({game_id: null, idle: false, name: {$ne: ''}},
                   {$set: {game_id: game_id}},
                   {multi: true});
    // Save a record of who is in the game, so when they leave we can
    // still show them.
    var p = Players.find({game_id: game_id},
                         {fields: {_id: true, name: true}}).fetch();
    Games.update({_id: game_id}, {$set: {players: p}});

    // wind down the game clock
    var clock = 120;
    var interval = Meteor.setInterval(function () {
      clock -= 1;
      Games.update(game_id, {$set: {clock: clock}});

      // end of game
      if (clock === 0) {
        // stop the clock
        Meteor.clearInterval(interval);
        // declare zero or more winners
        var scores = {};
        Words.find({game_id: game_id}).forEach(function (word) {
          if (!scores[word.player_id])
            scores[word.player_id] = 0;
          scores[word.player_id] += word.score;
        });
        var high_score = _.max(scores);
        var winners = [];
        _.each(scores, function (score, player_id) {
          if (score === high_score)
            winners.push(player_id);
        });
        Games.update(game_id, {$set: {winners: winners}});
      }
    }, 1000);

    return game_id;
  },

  keepalive: function (player_id) {
    check(player_id, String);
    Players.update({_id: player_id},
                  {$set: {last_keepalive: (new Date()).getTime(),
                          idle: false}});
  }
});

Meteor.setInterval(function () {
  var now = (new Date()).getTime();
  var idle_threshold = now - 70*1000; // 70 sec
  var remove_threshold = now - 60*60*1000; // 1hr

  Players.update({last_keepalive: {$lt: idle_threshold}},
                 {$set: {idle: true}});

  // XXX need to deal with people coming back!
  // Players.remove({$lt: {last_keepalive: remove_threshold}});

}, 30*1000);


Package.describe({
  summary: "Tests for JavaScript code analysis for Meteor",
  internal: true
});

// The tests are in a separate package so that it is possible to compile
// 'js-analyze' as a unipackage and then load it via `unipackage.load` without
// any dependencies.
Package.on_test(function (api) {
  api.use('tinytest');
  api.use('js-analyze');
  api.add_files('js_analyze_tests.js', 'server');
});


{
"dependencies": {
"mongodb": {
"version": "1.4.1",
"dependencies": {
"bson": {


var Future = require('fibers/future');
var readline = require('readline');
var _ = require('underscore');
var archinfo = require('./archinfo.js');
var files = require('./files.js');
var semver = require('semver');
var os = require('os');
var fs = require('fs');

var utils = exports;

// options:
//   - echo (boolean): defaults to true
//   - prompt (string)
//   - stream: defaults to process.stdout (you might want process.stderr)
exports.readLine = function (options) {
  var fut = new Future();

  options = _.extend({
    echo: true,
    stream: process.stdout
  }, options);

  var silentStream = {
    write: function () {
    },
    on: function () {
    },
    end: function () {
    },
    isTTY: options.stream.isTTY,
    removeListener: function () {
    }
  };

  // Read a line, throwing away the echoed characters into our dummy stream.
  var rl = readline.createInterface({
    input: process.stdin,
    output: options.echo ? options.stream : silentStream,
    // `terminal: options.stream.isTTY` is the default, but emacs shell users
    // don't want fancy ANSI.
    terminal: options.stream.isTTY && process.env.EMACS !== 't'
  });

  if (! options.echo) {
    options.stream.write(options.prompt);
  } else {
    rl.setPrompt(options.prompt);
    rl.prompt();
  }

  rl.on('line', function (line) {
    rl.close();
    if (! options.echo)
      options.stream.write("\n");
    fut['return'](line);
  });

  return fut.wait();
};

// Determine a human-readable hostname for this computer. Prefer names
// that make sense to users (eg, the name they manually gave their
// computer on OS X, which might contain spaces) over names that have
// any particular technical significance (eg, might resolve in DNS).
exports.getHost = function () {
  var ret;
  var attempt = function () {
    var output = files.run.apply(null, arguments);
    if (output) {
      ret = output.trim();
    }
  };

  if (archinfo.matches(archinfo.host(), 'os.osx')) {
    // On OSX, to get the human-readable hostname that the user chose,
    // we call:
    //   scutil --get ComputerName
    // This can contain spaces. See
    // http://osxdaily.com/2012/10/24/set-the-hostname-computer-name-and-bonjour-name-separately-in-os-x/
    if (! ret) attempt("scutil", "--get", "ComputerName");
  }

  if (archinfo.matches(archinfo.host(), 'os.osx') ||
      archinfo.matches(archinfo.host(), 'os.linux')) {
    // On Unix-like platforms, try passing -s to hostname to strip off
    // the domain name, to reduce the extent to which the output
    // varies with DNS.
    if (! ret) attempt("hostname", "-s");
  }

  // Try "hostname" on any platform. It should work on
  // Windows. Unknown platforms that have a command called "hostname"
  // that deletes all of your files deserve what the get.
  if (! ret) attempt("hostname");

  // Otherwise, see what Node can come up with.
  return ret || os.hostname();
};

// Return standard info about this user-agent. Used when logging in to
// Meteor Accounts, mostly so that when the user is seeing a list of
// their open sessions in their profile on the web, they have a way to
// decide which ones they want to revoke.
exports.getAgentInfo = function () {
  var ret = {};

  var host = utils.getHost();
  if (host)
    ret.host = host;
  ret.agent = "Meteor";
  ret.agentVersion =
    files.inCheckout() ? "checkout" : files.getToolsVersion();
  ret.arch = archinfo.host();

  return ret;
};

// Wait for 'ms' milliseconds, and then return. Yields. (Must be
// called within a fiber, and blocks only the calling fiber, not the
// whole program.)
exports.sleepMs = function (ms) {
  if (ms <= 0)
    return;

  var fut = new Future;
  setTimeout(function () { fut['return']() }, ms);
  fut.wait();
};

// Return a short, high entropy string without too many funny
// characters in it.
exports.randomToken = function () {
  return (Math.random() * 0x100000000 + 1).toString(36);
};

// Returns a random non-privileged port number.
exports.randomPort = function () {
  return 20000 + Math.floor(Math.random() * 10000);
};

(function () {
  var PackageVersion = null;

  var maybeLoadPackageVersionParser = function () {
    if (PackageVersion)
      return;
    var uniload = require('./uniload.js');
    var Package = uniload.load({packages: ['package-version-parser']});
    PackageVersion = Package['package-version-parser'].PackageVersion;
  };

  exports.parseVersionConstraint = function () {
    maybeLoadPackageVersionParser();
    return PackageVersion.parseVersionConstraint.apply(this, arguments);
  };
  exports.parseConstraint = function () {
    maybeLoadPackageVersionParser();
    return PackageVersion.parseConstraint.apply(this, arguments);
  };
})();

// XXX should unify this with utils.parseConstraint
exports.splitConstraint = function (constraint) {
  var m = constraint.split("@");
  var ret = { package: m[0] };
  if (m.length > 1) {
    ret.constraint = m[1];
  } else {
    ret.constraint = null;
  }
  return ret;
};

// XXX should unify this with utils.parseConstraint
exports.dealConstraint = function (constraint, pkg) {
  return { package: pkg, constraint: constraint};
};

// Check for invalid package names. Currently package names can only contain
// ASCII alphanumerics, dash, and dot, and must contain at least one letter. For
// safety reasons, package names may not start with a dot. Package names must be
// lowercase.
//
// This does not check that the package name is valid in terms of our naming
// scheme: ie, that it is prepended by a user's username. That check should
// happen at publication time.
exports.validPackageName = function (packageName) {
 if (/[^a-z0-9:.\-]/.test(packageName) || !/[a-z]/.test(packageName) ) {
   return false;
 }
 return true;
};

// True if this looks like a valid email address. We deliberately
// don't support
// - quoted usernames (eg, "foo"@bar.com, " "@bar.com, "@"@bar.com)
// - IP addresses in domains (eg, foo@1.2.3.4 or the IPv6 equivalent)
// because they're weird and we don't want them in our database.
exports.validEmail = function (address) {
  return /^[^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*@([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}$/.test(address);
};

// Like Perl's quotemeta: quotes all regexp metacharacters. See
//   https://github.com/substack/quotemeta/blob/master/index.js
exports.quotemeta = function (str) {
    return String(str).replace(/(\W)/g, '\\$1');
};

// If the given version matches a template (essentially, semver-style, but with
// a bounded number of digits per number part, and with no restriction on the
// amount of number parts, and some restrictions on legal prerelease labels),
// then return an orderKey for it. Otherwise return null.
//
// This conventional orderKey pads each part (with 0s for numbers, and ! for
// prerelease tags), and appends a $. (Because ! sorts before $, this means that
// the prerelease for a given release will sort before it. Because $ sorts
// before '.', this means that 1.2 will sort before 1.2.3.)
exports.defaultOrderKeyForReleaseVersion = function (v) {
  var m = v.match(/^(\d{1,4}(?:\.\d{1,4})*)(?:-([-A-za-z]{1,15})(\d{0,4}))?$/);
  if (!m)
    return null;
  var numberPart = m[1];
  var prereleaseTag = m[2];
  var prereleaseNumber = m[3];

  var hasRedundantLeadingZero = function (x) {
    return x.length > 1 && x[0] === '0';
  };
  var leftPad = function (chr, len, str) {
    if (str.length > len)
      throw Error("too long to pad!");
    var padding = new Array(len - str.length + 1).join(chr);
    return padding + str;
  };
  var rightPad = function (chr, len, str) {
    if (str.length > len)
      throw Error("too long to pad!");
    var padding = new Array(len - str.length + 1).join(chr);
    return str + padding;
  };

  // Versions must have no redundant leading zeroes, or else this encoding would
  // be ambiguous.
  var numbers = numberPart.split('.');
  if (_.any(numbers, hasRedundantLeadingZero))
    return null;
  if (prereleaseNumber && hasRedundantLeadingZero(prereleaseNumber))
    return null;

  // First, put together the non-prerelease part.
  var ret = _.map(numbers, _.partial(leftPad, '0', 4)).join('.');

  if (!prereleaseTag)
    return ret + '$';

  ret += '!' + rightPad('!', 15, prereleaseTag);
  if (prereleaseNumber)
    ret += leftPad('0', 4, prereleaseNumber);

  return ret + '$';
};

exports.isDirectory = function (dir) {
  try {
    // use stat rather than lstat since symlink to dir is OK
    var stats = fs.statSync(dir);
  } catch (e) {
    return false;
  }
  return stats.isDirectory();
};

// XXX from Underscore.String (http://epeli.github.com/underscore.string/)
exports.startsWith = function(str, starts) {
  return str.length >= starts.length &&
    str.substring(0, starts.length) === starts;
};

exports.displayRelease = function (track, version) {
  var catalog = require('./catalog.js');
  if (track === catalog.official.DEFAULT_TRACK)
    return "Meteor " + version;
  return track + '@' + version;
};

// Calls cb with each subset of the array "total", with non-decreasing size,
// until all subsets have been used or cb returns true. The array passed
// to cb may be safely mutated or retained by cb.
exports.generateSubsetsOfIncreasingSize = function (total, cb) {
  // We'll throw this if cb ever returns true, which is a simple way to pop us
  // out of our recursion.
  var Done = function () {};

  // Generates all subsets of size subsetSize which contain the indices already
  // in chosenIndices (and no indices that are "less than" any of them).
  var generateSubsetsOfFixedSize = function (goalSize, chosenIndices) {
    // If we've found a subset of the size we're looking for, output it.
    if (chosenIndices.length === goalSize) {
      // Change from indices into the actual elements. Note that 'elements' is
      // a newly allocated array which cb may mutate or retain.
      var elements = [];
      _.each(chosenIndices, function (index) {
        elements.push(total[index]);
      });
      if (cb(elements)) {
        throw new Done();  // unwind all the recursion
      }
      return;
    }

    // Otherwise try adding another index and call this recursively.  We're
    // trying to produce a sorted list of indices, so if there are already
    // indices, we start with the one after the biggest one we already have.
    var firstIndexToConsider = chosenIndices.length ?
          chosenIndices[chosenIndices.length - 1] + 1 : 0;
    for (var i = firstIndexToConsider; i < total.length; ++i) {
      var withThisChoice = _.clone(chosenIndices);
      withThisChoice.push(i);
      generateSubsetsOfFixedSize(goalSize, withThisChoice);
    }
  };

  try {
    for (var goalSize = 0; goalSize <= total.length; ++goalSize) {
      generateSubsetsOfFixedSize(goalSize, []);
    }
  } catch (e) {
    if (!(e instanceof Done))
      throw e;
  }
};


// XXX this file no longer makes sense in isolation.  take it apart as
// part file reorg on the 'ui' package
var globalHelpers = {};

UI.registerHelper = function (name, func) {
  globalHelpers[name] = func;
};

UI._globalHelper = function (name) {
  return globalHelpers[name];
};

Handlebars = {};
Handlebars.registerHelper = UI.registerHelper;

// Utility to HTML-escape a string.
UI._escape = Handlebars._escape = (function() {
  var escape_map = {
    "<": "<",
    ">": ">",
    '"': """,
    "'": "'",
    "`": "`", /* IE allows backtick-delimited attributes?? */
    "&": "&"
  };
  var escape_one = function(c) {
    return escape_map[c];
  };

  return function (x) {
    return x.replace(/[&<>"'`]/g, escape_one);
  };
})();

// Return these from {{...}} helpers to achieve the same as returning
// strings from {{{...}}} helpers
Handlebars.SafeString = function(string) {
  this.string = string;
};
Handlebars.SafeString.prototype.toString = function() {
  return this.string.toString();
};


// Empty program. Means we don't need a ctl package.
Package.describe({
  version: "1.0.0"
});


"dependencies": {
"bson": {
"version": "0.2.7",
"dependencies": {
"nan": {
"version": "0.8.0"
}
}

keep keep replace keep keep keep keep keep


# Meteor packages used by this project, one per line.
#
# 'meteor add' and 'meteor remove' will edit this file for you,
# but you can also edit it by hand.

standard-app-packages
autopublish
insecure


# html-tools

A lightweight HTML tokenizer and parser which outputs to the HTMLjs
object representation.  Special hooks allow the syntax to be extended
to parse an HTML-like template language like Spacebars.

```
HTML.parseFragment("<div class=greeting>Hello<br>World</div>")

=> HTML.DIV({'class':'greeting'}, "Hello", HTML.BR(), "World"))
```

This package is used by the Spacebars compiler, which normally only
runs at bundle time but can also be used at runtime on the client or
server.

## Invoking the Parser

`HTML.parseFragment(input, options)` - Takes an input string or Scanner object and returns HTMLjs.

In the basic case, where no options are passed, `parseFragment` will consume the entire input (the full string or the rest of the Scanner).

The options are as follows:

#### getSpecialTag

This option extends the HTML parser to parse template tags such as `{{foo}}`.

`getSpecialTag: function (scanner, templateTagPosition) { ... }` - A function for the parser to call after every HTML token and at various positions within tags.  If the function returns a non-null value, that value is wrapped in an `HTML.Special` node which is inserted into the HTMLjs tree at the appropriate location.  The function is expected to advance the scanner if it succeeds at parsing a template tag (see the section on `HTML.Scanner`).

There are four possible outcomes when `getSpecialTag` is called:

* Not a template tag - Leave the scanner as is, and return `null`.  A quick peek at the next character should bail to this case if the start of a template tag is not seen.
* Bad template tag - Call `scanner.fatal`, which aborts parsing completely.  Once the beginning of a template tag is seen, `getSpecialTag` will generally want to commit, and either succeed or fail trying).
* Good template tag - Advance the scanner to the end of the template tag and return an object.
* Comment tag - Advance the scanner and return `null`.  For example, a Spacebars comment is `{{! foo}}`.

The `templateTagPosition` argument to `getSpecialTag` is one of:

* `HTML.TEMPLATE_TAG_POSITION.ELEMENT` - At "element level," meaning somewhere an HTML tag could be.
* `HTML.TEMPLATE_TAG_POSITION.IN_START_TAG` - Inside a start tag, as in `<div {{foo}}>`, where you might otherwise find `name=value`.
* `HTML.TEMPLATE_TAG_POSITION.IN_ATTRIBUTE` - Inside the value of an HTML attribute, as in `<div class={{foo}}>`.
* `HTML.TEMPLATE_TAG_POSITION.IN_RCDATA` - Inside a TEXTAREA or a block helper inside an attribute, where character references are allowed ("replaced character data") but not tags.
* `HTML.TEMPLATE_TAG_POSITION.IN_RAWTEXT` - In a context where character references are not parsed, such as a script tag, style tag, or markdown helper.

It's completely normal for `getSpecialTag` to invoke `HTML.parseFragment` recursively on the same scanner (see `shouldStop`).  If it does so, the same value of `getSpecialTag` must be passed to the second invocation.

At the moment, template tags must begin with `{`.  The parser does not try calling `getSpecialTag` for every character of an HTML document, only at token boundaries, and it knows to always end a token at `{`.

**XXX Better error message for `<div {{k}}={{v}}>`.**

**XXX Do something with `<input type=checkbox {{#if foo}}checked{{/if}}>`**

**XXX Why both IN_ATTRIBUTE and IN_RCDATA?**

**XXX Fix Markdown**

#### textMode

The `textMode` option, if present, causes the parser to parse text (such as the contents of a `<textarea>` tag or part of an attribute) instead of HTML.  In a text mode, for example, the input `"<"` is not a parse error (because a bare `<` is allowed in a textarea or attribute).

The value of `textMode` must be one of:

* `HTML.TEXTMODE.RCDATA` - Interpret character references (the usual case)
* `HTML.TEXTMODE.STRING` - Don't interpret character references (the RAWTEXT case)

#### shouldStop

`shouldStop: function (scanner) { ... }` - A function that the parser invokes between tokens to check whether it should stop parsing.  The function should return a boolean value.

The `shouldStop` function provides a way to put a "wall" in the input stream for the purpose of parsing HTML content embedded in a template tag.  For example, take the template `{{#if happy}}yay{{/if}}`.  The scanner will be advanced to the start of the word `yay` before `parseFragment` is called to parse the contents of the tag.  (Note that the caller happens to be the `getSpecialTag` function of an enclosing `parseFragment`.)  When parsing from `yay`, the `shouldStop` function is used to end the fragment at `{{/if}}`, which, like `{{/blah}}` or `{{else}}`, couldn't possibly be actual content that belongs in the fragment.  Even if HTML tags are not closed, as in the malformed template `{{#if foo}}<div>{{else}}`, the fragment stops at the `{{else}}`, and the error is an unclosed `<div>` (before the parser notices the unclosed `{{#if}}`).

**XXX This option doesn't seem very elegant, or at least the way it's passed around internally isn't.**

## HTML.Scanner class

To write `getSpecialTag` and `shouldStop` functions, you have to
interface with the `HTML.Scanner` class used by html-tools.  It's a
general class that could be used by any parser/lexer/tokenizer.

A Scanner has an immutable source document and a mutable pointer into
the document.

* `new Scanner(input)` - constructs a Scanner with source string `input`
* `scanner.input` (read-only) - the entire source string
* `scanner.pos` (read/write) - the current index into the source string

Scanners provide these methods for convenience:

* `scanner.rest()` - `input.slice(pos)` (the rest of the document)
* `scanner.peek()` - `input.charAt(pos)` (the next character)
* `scanner.isEOF()` - true if `pos` is at or beyond the end of `input`
* `scanner.fatal(msg)` - throw an error indicating a problem at `pos`

Even though `scanner.rest()` performs a substring operation, it should be considered fast and O(1), because all known JavaScript runtimes in use have constant-time substring.  It would be possible, but extremely clumsy, to avoid such a substring operation while performing the usual business of a parser, which is to try to match a regex anchored at a particular index.

Functions that take scanners generally have three possible outcomes:

* Success:  Advance `scanner.pos` and return some truthy value
* Failure: Leave `scanner.pos` alone and return `null`
* Fatal: Throw an exception via `scanner.fatal`

It's particularly important that in the Failure case, the function restores the scanner to the state it found it.  This makes it possible to immediately try another parse function when one fails and form alternations such as `foo(scanner) || bar(scanner)`.

It's often easiest to avoid the Failure case altogether, writing parse functions that always succeed or throw.  This requires less bookkeeping and leads to good error messages.  A Failure case may be added if it is simple to check for up front and makes the function easier to use in an alternation.  We say a function has "committed" or "will succeed or fail fatally trying" when it has reached a point where it must return a value or throw.  Any parse function that has moved the scanner position and not remembered the original position is necessarily committed.  Usually, committing is completely natural in the context of the language being parsed; for example, `{{` in a template always starts a template tag or throws an error about a malformed template tag.

## HTML Dialect

HTML has many dialects and potential degrees of permissiveness.  We
use the WHATWG syntax spec and are pretty strict, failing on any
"parse error" cases, which basically means the input has to be
valid "HTML5" (except for the template tags).

HTML syntax references:

* [Human-readable syntax guide](http://developers.whatwg.org/syntax.html#syntax)
* [Tokenization state machine](http://www.whatwg.org/specs/web-apps/current-work/multipage/tokenization.html)

The WHATWG parser without error recovery is strict compared to
browsers (which will recover from almost anything), but lenient
compared to the now-defunct XHTML spec (which required lowercase tag
names and lots more escaping of special characters).

The following are examples of **errors**:

* A stray or unclosed `<` character
* An unknown character reference like `&asdf;`
* Self-closing tags like `<div/>` (except for BR, HR, INPUT, and other "void" elements)
* End tags for void elements (BR, HR, INPUT, etc.)
* Missing end tags, in most cases (e.g. missing `</div>`)

The following are **permitted**:

* Bare `>` characters
* Bare `&` that can't be confused with a character reference
* Uppercase or lowercase tag and attribute names (case insensitive)
* Unquoted and valueless attributes - `<input type=checkbox checked>`
* Most characters in attribute values - `<img alt=x,y>`
* Embedded SVG elements

**XXX Currently you have to close your Ps, LIs, and other tags for which the spec allows the end tag to be omitted in many cases**

## Character References

This package contains a lookup table for all known named character references in HTML, of which there are over 2,000, from `Á` (capital A, acute accent) to `` (zero-width non-joiner), as well as code for interpreting numeric character entities like `A`.

Since character references are parsed into `HTML.CharRef` objects which contain both the raw and interpreted form, we never have to convert between the forms except at parse time.


some paragraph to fix showdown's four space parsing below.

<b></b>

``

<b></b>


keep keep keep keep keep replace replace replace keep keep keep


  <title>movers</title>

  {{> main}}

<template name="main">

Red

Green

Blue

Yellow

</template>