text: // Copyright Joyent, Inc. and other Node contributors.
  //
  // Permission is hereby granted, free of charge, to any person obtaining a
  // copy of this software and associated documentation files (the
  // "Software"), to deal in the Software without restriction, including
  // without limitation the rights to use, copy, modify, merge, publish,
  // distribute, sublicense, and/or sell copies of the Software, and to permit
  // persons to whom the Software is furnished to do so, subject to the
  // following conditions:
  //
  // The above copyright notice and this permission notice shall be included
  // in all copies or substantial portions of the Software.
  //
  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
  // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
  // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
  // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
  // USE OR OTHER DEALINGS IN THE SOFTWARE.
  
  var Transform = require('_stream_transform');
  
  var binding = process.binding('zlib');
  var util = require('util');
  var assert = require('assert').ok;
  
  // zlib doesn't provide these, so kludge them in following the same
  // const naming scheme zlib uses.
  binding.Z_MIN_WINDOWBITS = 8;
  binding.Z_MAX_WINDOWBITS = 15;
  binding.Z_DEFAULT_WINDOWBITS = 15;
  
  // fewer than 64 bytes per chunk is stupid.
  // technically it could work with as few as 8, but even 64 bytes
  // is absurdly low.  Usually a MB or more is best.
  binding.Z_MIN_CHUNK = 64;
  binding.Z_MAX_CHUNK = Infinity;
  binding.Z_DEFAULT_CHUNK = (16 * 1024);
  
  binding.Z_MIN_MEMLEVEL = 1;
  binding.Z_MAX_MEMLEVEL = 9;
  binding.Z_DEFAULT_MEMLEVEL = 8;
  
  binding.Z_MIN_LEVEL = -1;
  binding.Z_MAX_LEVEL = 9;
  binding.Z_DEFAULT_LEVEL = binding.Z_DEFAULT_COMPRESSION;
  
  // expose all the zlib constants
  Object.keys(binding).forEach(function(k) {
    if (k.match(/^Z/)) exports[k] = binding[k];
  });
  
  // translation table for return codes.
  exports.codes = {
    Z_OK: binding.Z_OK,
    Z_STREAM_END: binding.Z_STREAM_END,
    Z_NEED_DICT: binding.Z_NEED_DICT,
    Z_ERRNO: binding.Z_ERRNO,
    Z_STREAM_ERROR: binding.Z_STREAM_ERROR,
    Z_DATA_ERROR: binding.Z_DATA_ERROR,
    Z_MEM_ERROR: binding.Z_MEM_ERROR,
    Z_BUF_ERROR: binding.Z_BUF_ERROR,
    Z_VERSION_ERROR: binding.Z_VERSION_ERROR
  };
  
  Object.keys(exports.codes).forEach(function(k) {
    exports.codes[exports.codes[k]] = k;
  });
  
  exports.Deflate = Deflate;
  exports.Inflate = Inflate;
  exports.Gzip = Gzip;
  exports.Gunzip = Gunzip;
  exports.DeflateRaw = DeflateRaw;
  exports.InflateRaw = InflateRaw;
  exports.Unzip = Unzip;
  
  exports.createDeflate = function(o) {
    return new Deflate(o);
  };
  
  exports.createInflate = function(o) {
    return new Inflate(o);
  };
  
  exports.createDeflateRaw = function(o) {
    return new DeflateRaw(o);
  };
  
  exports.createInflateRaw = function(o) {
    return new InflateRaw(o);
  };
  
  exports.createGzip = function(o) {
    return new Gzip(o);
  };
  
  exports.createGunzip = function(o) {
    return new Gunzip(o);
  };
  
  exports.createUnzip = function(o) {
    return new Unzip(o);
  };
  
  
  // Convenience methods.
  // compress/decompress a string or buffer in one step.
  exports.deflate = function(buffer, opts, callback) {
    if (util.isFunction(opts)) {
      callback = opts;
      opts = {};
    }
    zlibBuffer(new Deflate(opts), buffer, callback);
  };
  
  exports.gzip = function(buffer, opts, callback) {
    if (util.isFunction(opts)) {
      callback = opts;
      opts = {};
    }
    zlibBuffer(new Gzip(opts), buffer, callback);
  };
  
  exports.deflateRaw = function(buffer, opts, callback) {
    if (util.isFunction(opts)) {
      callback = opts;
      opts = {};
    }
    zlibBuffer(new DeflateRaw(opts), buffer, callback);
  };
  
  exports.unzip = function(buffer, opts, callback) {
    if (util.isFunction(opts)) {
      callback = opts;
      opts = {};
    }
    zlibBuffer(new Unzip(opts), buffer, callback);
  };
  
  exports.inflate = function(buffer, opts, callback) {
    if (util.isFunction(opts)) {
      callback = opts;
      opts = {};
    }
    zlibBuffer(new Inflate(opts), buffer, callback);
  };
  
  exports.gunzip = function(buffer, opts, callback) {
    if (util.isFunction(opts)) {
      callback = opts;
      opts = {};
    }
    zlibBuffer(new Gunzip(opts), buffer, callback);
  };
  
  exports.inflateRaw = function(buffer, opts, callback) {
    if (util.isFunction(opts)) {
      callback = opts;
      opts = {};
    }
    zlibBuffer(new InflateRaw(opts), buffer, callback);
  };
  
  function zlibBuffer(engine, buffer, callback) {
    var buffers = [];
    var nread = 0;
  
    engine.on('error', onError);
    engine.on('end', onEnd);
  
    engine.end(buffer);
    flow();
  
    function flow() {
      var chunk;
      while (null !== (chunk = engine.read())) {
        buffers.push(chunk);
        nread += chunk.length;
      }
      engine.once('readable', flow);
    }
  
    function onError(err) {
      engine.removeListener('end', onEnd);
      engine.removeListener('readable', flow);
      callback(err);
    }
  
    function onEnd() {
      var buf = Buffer.concat(buffers, nread);
      buffers = [];
      callback(null, buf);
      engine.close();
    }
  }
  
  
  // generic zlib
  // minimal 2-byte header
  function Deflate(opts) {
    if (!(this instanceof Deflate)) return new Deflate(opts);
    Zlib.call(this, opts, binding.DEFLATE);
  }
  
  function Inflate(opts) {
    if (!(this instanceof Inflate)) return new Inflate(opts);
    Zlib.call(this, opts, binding.INFLATE);
  }
  
  
  
  // gzip - bigger header, same deflate compression
  function Gzip(opts) {
    if (!(this instanceof Gzip)) return new Gzip(opts);
    Zlib.call(this, opts, binding.GZIP);
  }
  
  function Gunzip(opts) {
    if (!(this instanceof Gunzip)) return new Gunzip(opts);
    Zlib.call(this, opts, binding.GUNZIP);
  }
  
  
  
  // raw - no header
  function DeflateRaw(opts) {
    if (!(this instanceof DeflateRaw)) return new DeflateRaw(opts);
    Zlib.call(this, opts, binding.DEFLATERAW);
  }
  
  function InflateRaw(opts) {
    if (!(this instanceof InflateRaw)) return new InflateRaw(opts);
    Zlib.call(this, opts, binding.INFLATERAW);
  }
  
  
  // auto-detect header.
  function Unzip(opts) {
    if (!(this instanceof Unzip)) return new Unzip(opts);
    Zlib.call(this, opts, binding.UNZIP);
  }
  
  
  // the Zlib class they all inherit from
  // This thing manages the queue of requests, and returns
  // true or false if there is anything in the queue when
  // you call the .write() method.
  
  function Zlib(opts, mode) {
    this._opts = opts = opts || {};
    this._chunkSize = opts.chunkSize || exports.Z_DEFAULT_CHUNK;
  
    Transform.call(this, opts);
  
    if (opts.flush) {
      if (opts.flush !== binding.Z_NO_FLUSH &&
          opts.flush !== binding.Z_PARTIAL_FLUSH &&
          opts.flush !== binding.Z_SYNC_FLUSH &&
          opts.flush !== binding.Z_FULL_FLUSH &&
          opts.flush !== binding.Z_FINISH &&
          opts.flush !== binding.Z_BLOCK) {
        throw new Error('Invalid flush flag: ' + opts.flush);
      }
    }
    this._flushFlag = opts.flush || binding.Z_NO_FLUSH;
  
    if (opts.chunkSize) {
      if (opts.chunkSize < exports.Z_MIN_CHUNK ||
          opts.chunkSize > exports.Z_MAX_CHUNK) {
        throw new Error('Invalid chunk size: ' + opts.chunkSize);
      }
    }
  
    if (opts.windowBits) {
      if (opts.windowBits < exports.Z_MIN_WINDOWBITS ||
          opts.windowBits > exports.Z_MAX_WINDOWBITS) {
        throw new Error('Invalid windowBits: ' + opts.windowBits);
      }
    }
  
    if (opts.level) {
      if (opts.level < exports.Z_MIN_LEVEL ||
          opts.level > exports.Z_MAX_LEVEL) {
        throw new Error('Invalid compression level: ' + opts.level);
      }
    }
  
    if (opts.memLevel) {
      if (opts.memLevel < exports.Z_MIN_MEMLEVEL ||
          opts.memLevel > exports.Z_MAX_MEMLEVEL) {
        throw new Error('Invalid memLevel: ' + opts.memLevel);
      }
    }
  
    if (opts.strategy) {
      if (opts.strategy != exports.Z_FILTERED &&
          opts.strategy != exports.Z_HUFFMAN_ONLY &&
          opts.strategy != exports.Z_RLE &&
          opts.strategy != exports.Z_FIXED &&
          opts.strategy != exports.Z_DEFAULT_STRATEGY) {
        throw new Error('Invalid strategy: ' + opts.strategy);
      }
    }
  
    if (opts.dictionary) {
      if (!util.isBuffer(opts.dictionary)) {
        throw new Error('Invalid dictionary: it should be a Buffer instance');
      }
    }
  
    this._binding = new binding.Zlib(mode);
  
    var self = this;
    this._hadError = false;
    this._binding.onerror = function(message, errno) {
      // there is no way to cleanly recover.
      // continuing only obscures problems.
      self._binding = null;
      self._hadError = true;
  
      var error = new Error(message);
      error.errno = errno;
      error.code = exports.codes[errno];
      self.emit('error', error);
    };
  
    var level = exports.Z_DEFAULT_COMPRESSION;
    if (util.isNumber(opts.level)) level = opts.level;
  
    var strategy = exports.Z_DEFAULT_STRATEGY;
    if (util.isNumber(opts.strategy)) strategy = opts.strategy;
  
    this._binding.init(opts.windowBits || exports.Z_DEFAULT_WINDOWBITS,
                       level,
                       opts.memLevel || exports.Z_DEFAULT_MEMLEVEL,
                       strategy,
                       opts.dictionary);
  
    this._buffer = new Buffer(this._chunkSize);
    this._offset = 0;
    this._closed = false;
    this._level = level;
    this._strategy = strategy;
  
    this.once('end', this.close);
  }
  
  util.inherits(Zlib, Transform);
  
  Zlib.prototype.params = function(level, strategy, callback) {
    if (level < exports.Z_MIN_LEVEL ||
        level > exports.Z_MAX_LEVEL) {
      throw new RangeError('Invalid compression level: ' + level);
    }
    if (strategy != exports.Z_FILTERED &&
        strategy != exports.Z_HUFFMAN_ONLY &&
        strategy != exports.Z_RLE &&
        strategy != exports.Z_FIXED &&
        strategy != exports.Z_DEFAULT_STRATEGY) {
      throw new TypeError('Invalid strategy: ' + strategy);
    }
  
    if (this._level !== level || this._strategy !== strategy) {
      var self = this;
      this.flush(binding.Z_SYNC_FLUSH, function() {
        self._binding.params(level, strategy);
        if (!self._hadError) {
          self._level = level;
          self._strategy = strategy;
          if (callback) callback();
        }
      });
    } else {
      process.nextTick(callback);
    }
  };
  
  Zlib.prototype.reset = function() {
    return this._binding.reset();
  };
  
  // This is the _flush function called by the transform class,
  // internally, when the last chunk has been written.
  Zlib.prototype._flush = function(callback) {
    this._transform(new Buffer(0), '', callback);
  };
  
  Zlib.prototype.flush = function(kind, callback) {
    var ws = this._writableState;
  
    if (util.isFunction(kind) || (util.isUndefined(kind) && !callback)) {
      callback = kind;
      kind = binding.Z_FULL_FLUSH;
    }
  
    if (ws.ended) {
      if (callback)
        process.nextTick(callback);
    } else if (ws.ending) {
      if (callback)
        this.once('end', callback);
    } else if (ws.needDrain) {
      var self = this;
      this.once('drain', function() {
        self.flush(callback);
      });
    } else {
      this._flushFlag = kind;
      this.write(new Buffer(0), '', callback);
    }
  };
  
  Zlib.prototype.close = function(callback) {
    if (callback)
      process.nextTick(callback);
  
    if (this._closed)
      return;
  
    this._closed = true;
  
    this._binding.close();
  
    var self = this;
    process.nextTick(function() {
      self.emit('close');
    });
  };
  
  Zlib.prototype._transform = function(chunk, encoding, cb) {
    var flushFlag;
    var ws = this._writableState;
    var ending = ws.ending || ws.ended;
    var last = ending && (!chunk || ws.length === chunk.length);
  
    if (!util.isNull(chunk) && !util.isBuffer(chunk))
      return cb(new Error('invalid input'));
  
    // If it's the last chunk, or a final flush, we use the Z_FINISH flush flag.
    // If it's explicitly flushing at some other time, then we use
    // Z_FULL_FLUSH. Otherwise, use Z_NO_FLUSH for maximum compression
    // goodness.
    if (last)
      flushFlag = binding.Z_FINISH;
    else {
      flushFlag = this._flushFlag;
      // once we've flushed the last of the queue, stop flushing and
      // go back to the normal behavior.
      if (chunk.length >= ws.length) {
        this._flushFlag = this._opts.flush || binding.Z_NO_FLUSH;
      }
    }
  
    var availInBefore = chunk && chunk.length;
    var availOutBefore = this._chunkSize - this._offset;
    var inOff = 0;
  
    var req = this._binding.write(flushFlag,
                                  chunk, // in
                                  inOff, // in_off
                                  availInBefore, // in_len
                                  this._buffer, // out
                                  this._offset, //out_off
                                  availOutBefore); // out_len
  
    req.buffer = chunk;
    req.callback = callback;
  
    var self = this;
    function callback(availInAfter, availOutAfter) {
      if (self._hadError)
        return;
  
      var have = availOutBefore - availOutAfter;
      assert(have >= 0, 'have should not go down');
  
      if (have > 0) {
        var out = self._buffer.slice(self._offset, self._offset + have);
        self._offset += have;
        // serve some output to the consumer.
        self.push(out);
      }
  
      // exhausted the output buffer, or used all the input create a new one.
      if (availOutAfter === 0 || self._offset >= self._chunkSize) {
        availOutBefore = self._chunkSize;
        self._offset = 0;
        self._buffer = new Buffer(self._chunkSize);
      }
  
      if (availOutAfter === 0) {
        // Not actually done.  Need to reprocess.
        // Also, update the availInBefore to the availInAfter value,
        // so that if we have to hit it a third (fourth, etc.) time,
        // it'll have the correct byte counts.
        inOff += (availInBefore - availInAfter);
        availInBefore = availInAfter;
  
        var newReq = self._binding.write(flushFlag,
                                         chunk,
                                         inOff,
                                         availInBefore,
                                         self._buffer,
                                         self._offset,
                                         self._chunkSize);
        newReq.callback = callback; // this same function
        newReq.buffer = chunk;
        return;
      }
  
      // finished with the chunk.
      cb();
    }
  };
  
  util.inherits(Deflate, Zlib);
  util.inherits(Inflate, Zlib);
  util.inherits(Gzip, Zlib);
  util.inherits(Gunzip, Zlib);
  util.inherits(DeflateRaw, Zlib);
  util.inherits(InflateRaw, Zlib);
  util.inherits(Unzip, Zlib);
  