Source: read.js

'use strict';

module.exports = function(Fireproof) {

  /**
   * Delegates Firebase#transaction.
   * @method Fireproof#transaction
   * @param {function} updateFunction
   * @param {function} onComplete
   * @param {boolean=} applyLocally
   * @returns {Promise} Resolves on success, rejects on failure.
   */
  Fireproof.prototype.transaction = function(updateFunction, onComplete, applyLocally) {

    var deferred = Fireproof._checkQ().defer(),
      self = this;

    Fireproof.stats._addListener(self.toString());

    self._ref.transaction(updateFunction, function(err, committed, snap) {

      Fireproof.stats._record('read', self.toString());
      snap = new Fireproof.Snapshot(snap);

      Fireproof._nextTick(function() {

        if (onComplete) {
          onComplete(err, committed, snap);
        }

        if (err) {
          deferred.reject(err);
        } else {

          deferred.resolve({
            committed: committed,
            snapshot: snap
          });

        }

      });

    }, applyLocally);

    return deferred.promise;

  };


  /**
   * Delegates Firebase#on.
   * @method Fireproof#on
   * @param {string} eventType
   * @param {function} callback
   * @param {function=} cancelCallback
   * @param {object=} context
   * @returns {function} Your callback parameter wrapped in fireproofing. Use
   * this return value, not your own copy of callback, to call .off(). It also
   * functions as a promise that resolves on success and rejects on failure.
   */
  Fireproof.prototype.on = function(eventType, callback, cancelCallback, context) {

    var deferred = Fireproof._checkQ().defer(),
      resolved = false,
      self = this;

    Fireproof.stats._addListener(self.toString());

    if (typeof callback !== 'function') {
      callback = function() {};
    }

    if (typeof cancelCallback !== 'function') {
      cancelCallback = function() {};
    }

    var callbackHandler = function(snap, prev) {

      Fireproof.stats._record('read', self.toString());
      snap = new Fireproof.Snapshot(snap);

      Fireproof._nextTick(function() {

        callback(snap, prev);
        if (!resolved) {
          resolved = true;
          deferred.resolve(snap, prev);
        }

      });

    };

    callbackHandler.then = deferred.promise.then.bind(deferred.promise);

    self._ref.on(eventType, callbackHandler, function(err) {

      Fireproof.stats._removeListener(self.toString());

      Fireproof._nextTick(function() {
        cancelCallback(err);

        if (!resolved) {
          resolved = true;
          deferred.reject(err);
        }

      });

    }, context);

    return callbackHandler;

  };


  /**
   * Delegates Firebase#off.
   * @method Fireproof#off
   * @param {string} eventType
   * @param {function=} callback
   * @param {object=} context
   */
  Fireproof.prototype.off = function(eventType, callback, context) {

    Fireproof.stats._removeListener(this.toString());
    this._ref.off(eventType, callback, context);

  };


  /**
   * Delegates Firebase#once.
   * @method Fireproof#once
   * @param {object} eventType
   * @param {function} successCallback
   * @param {function=} failureCallback
   * @param {object=} context
   * @returns {Promise} Resolves on success and rejects on failure.
   */
  Fireproof.prototype.once = function(eventType, successCallback, failureCallback, context) {

    var deferred = Fireproof._checkQ().defer(),
      self = this;

    Fireproof.stats._addListener(self.toString());

    if (typeof successCallback !== 'function') {
      successCallback = function() {};
    }

    if (typeof failureCallback !== 'function') {
      failureCallback = function() {};
    }

    self._ref.once(eventType, function(snap) {

      Fireproof.stats._removeListener(self.toString());
      Fireproof.stats._record('read', self.toString());
      snap = new Fireproof.Snapshot(snap);

      deferred.resolve(snap);
      Fireproof._nextTick(function() {
        successCallback(snap);
      });

    }, function(err) {

      Fireproof.stats._removeListener(self.toString());
      deferred.reject(err);
      Fireproof._nextTick(function() {
        failureCallback(err);
      });

    }, context);

    return deferred.promise;

  };

};