text: var util = require('util');
  var Duplex = require('stream').Duplex;
  var bind = require('component-bind');
  var uuid = require('./uuid');
  var debug = require('debug')('socket.io-stream:iostream');
  
  
  module.exports = IOStream;
  
  util.inherits(IOStream, Duplex);
  
  /**
   * Duplex
   *
   * @param {Object} options
   * @api private
   */
  function IOStream(options) {
    if (!(this instanceof IOStream)) {
      return new IOStream(options);
    }
  
    IOStream.super_.call(this, options);
  
    this.options = options;
    this.id = uuid();
    this.socket = null;
  
    // Buffers
    this.pushBuffer = [];
    this.writeBuffer = [];
  
    // Op states
    this._readable = false;
    this._writable = false;
    this.destroyed = false;
  
    // default to *not* allowing half open sockets
    this.allowHalfOpen = options && options.allowHalfOpen || false;
  
    this.on('finish', this._onfinish);
    this.on('end', this._onend);
    this.on('error', this._onerror);
  }
  
  /**
   * Ensures that no more I/O activity happens on this stream.
   * Not necessary in the usual case.
   *
   * @api public
   */
  IOStream.prototype.destroy = function() {
    debug('destroy');
  
    if (this.destroyed) {
      debug('already destroyed');
      return;
    }
  
    this.readable = this.writable = false;
  
    if (this.socket) {
      debug('clean up');
      this.socket.cleanup(this.id);
      this.socket = null;
    }
  
    this.destroyed = true;
  };
  
  /**
   * Local read
   *
   * @api private
   */
  IOStream.prototype._read = function(size) {
    var push;
  
    // We can not read from the socket if it's destroyed obviously ...
    if (this.destroyed) return;
    // We also can't read from the socket if its null for some reason ...
    if(this.socket === null) return;
  
    if (this.pushBuffer.length) {
      // flush buffer and end if it exists.
      while (push = this.pushBuffer.shift()) {
        if (!push()) break;
      }
      return;
    }
  
    this._readable = true;
  
    // Go get data from remote stream
    // Calls
    // ._onread remotely
    // then
    // ._onwrite locally
    this.socket._read(this.id, size);
  };
  
  
  /**
   * Read from remote stream
   *
   * @api private
   */
  IOStream.prototype._onread = function(size) {
    var write = this.writeBuffer.shift();
    if (write) return write();
  
    this._writable = true;
  };
  
  /**
   * Write local data to remote stream
   * Calls
   * remtote ._onwrite
   *
   * @api private
   */
  IOStream.prototype._write = function(chunk, encoding, callback) {
    var self = this;
  
    function write() {
      // We can not write to the socket if it's destroyed obviously ...
      if (self.destroyed) return;
  
      self._writable = false;
      self.socket._write(self.id, chunk, encoding, callback);
    }
  
    if (this._writable) {
      write();
    } else {
      this.writeBuffer.push(write);
    }
  };
  
  /**
   * Write the data fetched remotely
   * so that we can now read locally
   *
   * @api private
   */
  IOStream.prototype._onwrite = function(chunk, encoding, callback) {
    var self = this;
  
    function push() {
      self._readable = false;
      var ret = self.push(chunk || '', encoding);
      callback();
      return ret;
    }
  
    if (this._readable) {
      push();
    } else {
      this.pushBuffer.push(push);
    }
  };
  
  /**
   * When ending send 'end' event to remote stream
   *
   * @api private
   */
  IOStream.prototype._end = function() {
    if (this.pushBuffer.length) {
      // end after flushing buffer.
      this.pushBuffer.push(bind(this, '_done'));
    } else {
      this._done();
    }
  };
  
  /**
   * Remote stream just ended
   *
   * @api private
   */
  IOStream.prototype._done = function() {
    this._readable = false;
  
    // signal the end of the data.
    return this.push(null);
  };
  
  /**
   * the user has called .end(), and all the bytes have been
   * sent out to the other side.
   * If allowHalfOpen is false, or if the readable side has
   * ended already, then destroy.
   * If allowHalfOpen is true, then we need to set writable false,
   * so that only the writable side will be cleaned up.
   *
   * @api private
   */
  IOStream.prototype._onfinish = function() {
    debug('_onfinish');
    // Local socket just finished
    // send 'end' event to remote
    if (this.socket) {
      this.socket._end(this.id);
    }
  
    this.writable = false;
    this._writableState.ended = true;
  
    if (!this.readable || this._readableState.ended) {
      debug('_onfinish: ended, destroy %s', this._readableState);
      return this.destroy();
    }
  
    debug('_onfinish: not ended');
  
    if (!this.allowHalfOpen) {
      this.push(null);
  
      // just in case we're waiting for an EOF.
      if (this.readable && !this._readableState.endEmitted) {
        this.read(0);
      }
    }
  };
  
  /**
   * the EOF has been received, and no more bytes are coming.
   * if the writable side has ended already, then clean everything
   * up.
   *
   * @api private
   */
  IOStream.prototype._onend = function() {
    debug('_onend');
    this.readable = false;
    this._readableState.ended = true;
  
    if (!this.writable || this._writableState.finished) {
      debug('_onend: %s', this._writableState);
      return this.destroy();
    }
  
    debug('_onend: not finished');
  
    if (!this.allowHalfOpen) {
      this.end();
    }
  };
  
  /**
   * When error in local stream
   * notyify remote
   * if err.remote = true
   * then error happened on remote stream
   *
   * @api private
   */
  IOStream.prototype._onerror = function(err) {
    // check if the error came from remote stream.
    if (!err.remote && this.socket) {
      // notify the error to the corresponding remote stream.
      this.socket._error(this.id, err);
    }
  
    this.destroy();
  };
  