
/*!
 * Connect - compress
 * Copyright(c) 2010 Sencha Inc.
 * Copyright(c) 2011 TJ Holowaychuk
 * Copyright(c) 2012 Nebojsa Sabovic
 * MIT Licensed
 */

/**
 * Module dependencies.
 */

var zlib = require('zlib');

/**
 * Supported content-encoding methods.
 */

exports.methods = {
    gzip: zlib.createGzip
  , deflate: zlib.createDeflate
};

/**
 * Default filter function.
 */

exports.filter = function(req, res){
  var type = res.getHeader('Content-Type') || '';
  return type.match(/json|text|javascript/);
};

/**
 * Compress:
 *
 * Compress response data with gzip/deflate.
 *
 * Filter:
 *
 *  A `filter` callback function may be passed to
 *  replace the default logic of:
 *
 *     exports.filter = function(req, res){
 *       var type = res.getHeader('Content-Type') || '';
 *       return type.match(/json|text|javascript/);
 *     };
 *
 * Options:
 *
 *  All remaining options are passed to the gzip/deflate
 *  creation functions. Consult node's docs for additional details.
 *
 *   - `chunkSize` (default: 16*1024)
 *   - `windowBits`
 *   - `level`: 0-9 where 0 is no compression, and 9 is slow but best compression
 *   - `memLevel`: 1-9 low is slower but uses less memory, high is fast but uses more
 *   - `strategy`: compression strategy
 *
 * @param {Object} options
 * @return {Function}
 * @api public
 */

module.exports = function compress(options) {
  var options = options || {}
    , names = Object.keys(exports.methods)
    , filter = options.filter || exports.filter;

  return function(req, res, next){
    var accept = req.headers['accept-encoding']
      , write = res.write
      , writeHead = res.writeHead
      , end = res.end
      , stream
      , method;

    // vary
    res.setHeader('Vary', 'Accept-Encoding');

    // proxy

    res.write = function(chunk, encoding){
      if (!this._header) this._implicitHeader();
      return stream
        ? stream.write(chunk, encoding)
        : write.call(this, chunk, encoding);
    };

    res.end = function(chunk, encoding){
      if (!this._header) {
        this._implicitHeader();
      }
      if (chunk) this.write(chunk, encoding);
      return stream
        ? stream.end()
        : end.call(this);
    };

    res.writeHead = function(){
      res.writeHead = writeHead;

      // default request filter, SHOULD use identity, head
      if (filter(req, res) && accept && 'HEAD' != req.method) {
        // default to gzip
        if ('*' == accept.trim()) {
          method = 'gzip';
        } else {
          for (var i = 0, len = names.length; i < len; ++i) {
            if (~accept.indexOf(names[i])) {
              method = names[i];
              break;
            }
          }
        }

        // compression method
        if (method) {
          // compression stream
          stream = exports.methods[method](options);

          // header fields
          res.setHeader('Content-Encoding', method);
          res.removeHeader('Content-Length');
          // WARNING: We do not strip content-length out of passed-in headers

          // compression
          stream.on('data', function(chunk){
            write.call(res, chunk);
          });

          stream.on('end', function(){
            end.call(res);
          });

          stream.on('drain', function() {
            res.emit('drain');
          });
        }
      }
      return res.writeHead.apply(this, arguments);
    };

    next();
  };
}
