var spdy = require('../spdy'),
    utils = exports;

var stream = require('stream'),
    zlib = require('zlib'),
    Buffer = require('buffer').Buffer;

// Export streams related stuff
utils.isLegacy = !stream.Duplex;
if (utils.isLegacy)
  utils.DuplexStream = stream;
else
  utils.DuplexStream = stream.Duplex;

//
// ### function createDeflate (version, compression)
// #### @version {Number} SPDY version
// #### @compression {Boolean} whether to enable compression
// Creates deflate stream with SPDY dictionary
//
utils.createDeflate = function createDeflate(version, compression) {
  var deflate = zlib.createDeflate({
    dictionary: spdy.protocol.dictionary[version],
    flush: zlib.Z_SYNC_FLUSH,
    windowBits: 11,
    level: compression ? zlib.Z_DEFAULT_COMPRESSION : zlib.Z_NO_COMPRESSION
  });

  // Define lock information early
  deflate.locked = false;
  deflate.lockQueue = [];
  if (spdy.utils.isLegacy)
    deflate._flush = zlib.Z_SYNC_FLUSH;

  return deflate;
};

//
// ### function createInflate (version)
// #### @version {Number} SPDY version
// Creates inflate stream with SPDY dictionary
//
utils.createInflate = function createInflate(version) {
  var inflate = zlib.createInflate({
    dictionary: spdy.protocol.dictionary[version],
    flush: zlib.Z_SYNC_FLUSH,
    windowBits: 15
  });

  // Define lock information early
  inflate.locked = false;
  inflate.lockQueue = [];
  if (spdy.utils.isLegacy)
    inflate._flush = zlib.Z_SYNC_FLUSH;

  return inflate;
};

//
// ### function resetZlibStream (stream)
// #### @stream {zlib.Stream} stream
// Resets zlib stream and associated locks
//
utils.resetZlibStream = function resetZlibStream(stream, callback) {
  if (stream.locked) {
    stream.lockQueue.push(function() {
      resetZlibStream(stream, callback);
    });
    return;
  }

  stream.reset();
  stream.lockQueue = [];

  callback(null);
};

var delta = 0;
//
// ### function zstream (stream, buffer, callback)
// #### @stream {Deflate|Inflate} One of streams above
// #### @buffer {Buffer} Input data (to compress or to decompress)
// #### @callback {Function} Continuation to callback
// Compress/decompress data and pass it to callback
//
utils.zstream = function zstream(stream, buffer, callback) {
  var chunks = [],
      total = 0;

  if (stream.locked) {
    stream.lockQueue.push(function() {
      zstream(stream, buffer, callback);
    });
    return;
  }
  stream.locked = true;

  function collect(chunk) {
    chunks.push(chunk);
    total += chunk.length;
  }
  stream.on('data', collect);

  stream.write(buffer, done);

  function done() {
    stream.removeAllListeners('data');
    stream.removeAllListeners('error');

    if (callback)
      callback(null, chunks, total);

    stream.locked = false;
    var deferred = stream.lockQueue.shift();
    if (deferred)
      deferred();
  };

  stream.once('error', function(err) {
    stream.removeAllListeners('data');
    callback(err);
    callback = null;
  });
};

//
// ### function zwrap (stream)
// #### @stream {zlib.Stream} stream to wrap
// Wraps stream within function to allow simple deflate/inflate
//
utils.zwrap = function zwrap(stream) {
  return function(data, callback) {
    utils.zstream(stream, data, callback);
  };
};

if (typeof setImmediate === 'undefined')
  utils.nextTick = process.nextTick.bind(process);
else
  utils.nextTick = setImmediate;
