text: var util = require('util');
  var zlib = require('zlib');
  var Stream = require('stream');
  var binary = require('binary');
  var Promise = require('bluebird');
  var PullStream = require('./PullStream');
  var NoopStream = require('./NoopStream');
  var BufferStream = require('./BufferStream');
  
  // Backwards compatibility for node 0.8
  if (!Stream.Writable)
    Stream = require('readable-stream');
  
  var endDirectorySignature = new Buffer(4);
  endDirectorySignature.writeUInt32LE(0x06054b50, 0);
  
  function Parse(opts) {
    if (!(this instanceof Parse)) {
      return new Parse(opts);
    }
    var self = this;
    self._opts = opts || { verbose: false };
  
    PullStream.call(self, self._opts);
    self.on('finish',function() {
      self.emit('close');
    });
    self._readRecord();
  }
  
  util.inherits(Parse, PullStream);
  
  Parse.prototype._readRecord = function () {
    var self = this;
    return self.pull(4).then(function(data) {
      if (data.length === 0)
        return;
  
      var signature = data.readUInt32LE(0);
  
      if (signature === 0x04034b50) {
        return self._readFile();
      }
      else if (signature === 0x02014b50) {
        self.__ended = true;
        return self._readCentralDirectoryFileHeader();
      }
      else if (signature === 0x06054b50) {
        return self._readEndOfCentralDirectoryRecord();
      }
      else if (self.__ended) {
        return self.pull(endDirectorySignature).then(function() {
            return self._readEndOfCentralDirectoryRecord();
          });
      }
      else
        self.emit('error', new Error('invalid signature: 0x' + signature.toString(16)));
    });
  };
  
  Parse.prototype._readFile = function () {
    var self = this;
    self.pull(26).then(function(data) {
      var vars = binary.parse(data)
        .word16lu('versionsNeededToExtract')
        .word16lu('flags')
        .word16lu('compressionMethod')
        .word16lu('lastModifiedTime')
        .word16lu('lastModifiedDate')
        .word32lu('crc32')
        .word32lu('compressedSize')
        .word32lu('uncompressedSize')
        .word16lu('fileNameLength')
        .word16lu('extraFieldLength')
        .vars;
  
      return self.pull(vars.fileNameLength).then(function(fileName) {
        fileName = fileName.toString('utf8');
        var entry = Stream.PassThrough();
        
        entry.autodrain = function() {
          return new Promise(function(resolve,reject) {
            entry.pipe(NoopStream());
            entry.on('finish',resolve);
            entry.on('error',reject);
          });
        };
  
        entry.buffer = function() {
          return BufferStream(entry);
        };
  
        entry.path = fileName;
        entry.props = {};
        entry.props.path = fileName;
        entry.type = (vars.uncompressedSize === 0 && /[\/\\]$/.test(fileName)) ? 'Directory' : 'File';
  
        if (self._opts.verbose) {
          if (entry.type === 'Directory') {
            console.log('   creating:', fileName);
          } else if (entry.type === 'File') {
            if (vars.compressionMethod === 0) {
              console.log(' extracting:', fileName);
            } else {
              console.log('  inflating:', fileName);
            }
          }
        }
         
        self.pull(vars.extraFieldLength).then(function(extraField) {
          var extra = binary.parse(extraField)
            .word16lu('signature')
            .word16lu('partsize')
            .word64lu('uncompressedSize')
            .word64lu('compressedSize')
            .word64lu('offset')
            .word64lu('disknum')
            .vars;
         
          if (vars.compressedSize === 0xffffffff)
            vars.compressedSize = extra.compressedSize;
          
          if (vars.uncompressedSize  === 0xffffffff)
            vars.uncompressedSize= extra.uncompressedSize;
  
          entry.vars = vars;
          entry.extra = extra;
  
          self.emit('entry', entry);
  
          if (self._readableState.pipesCount)
            self.push(entry);
  
          if (self._opts.verbose)
            console.log({
              filename:fileName,
              vars: vars,
              extra: extra
            });
  
          var fileSizeKnown = !(vars.flags & 0x08),
              eof;
  
          var inflater = vars.compressionMethod ? zlib.createInflateRaw() : Stream.PassThrough();
  
          if (fileSizeKnown) {
            entry.size = vars.uncompressedSize;
            eof = vars.compressedSize;
          } else {
            eof = new Buffer(4);
            eof.writeUInt32LE(0x08074b50, 0);
          }
  
          self.stream(eof)
            .pipe(inflater)
            .on('error',function(err) { self.emit('error',err);})
            .pipe(entry)
            .on('finish', function() {
              return fileSizeKnown ? self._readRecord() : self._processDataDescriptor(entry);
            });
        });
      });
    });
  };
  
  Parse.prototype._processDataDescriptor = function (entry) {
    var self = this;
    self.pull(16).then(function(data) {
      var vars = binary.parse(data)
        .word32lu('dataDescriptorSignature')
        .word32lu('crc32')
        .word32lu('compressedSize')
        .word32lu('uncompressedSize')
        .vars;
  
      entry.size = vars.uncompressedSize;
      self._readRecord();
    });
  };
  
  Parse.prototype._readCentralDirectoryFileHeader = function () {
    var self = this;
    self.pull(42).then(function(data) {
      
      var vars = binary.parse(data)
        .word16lu('versionMadeBy')
        .word16lu('versionsNeededToExtract')
        .word16lu('flags')
        .word16lu('compressionMethod')
        .word16lu('lastModifiedTime')
        .word16lu('lastModifiedDate')
        .word32lu('crc32')
        .word32lu('compressedSize')
        .word32lu('uncompressedSize')
        .word16lu('fileNameLength')
        .word16lu('extraFieldLength')
        .word16lu('fileCommentLength')
        .word16lu('diskNumber')
        .word16lu('internalFileAttributes')
        .word32lu('externalFileAttributes')
        .word32lu('offsetToLocalFileHeader')
        .vars;
  
      return self.pull(vars.fileNameLength).then(function(fileName) {
        vars.fileName = fileName.toString('utf8');
        return self.pull(vars.extraFieldLength);
      })
      .then(function(extraField) {
        return self.pull(vars.fileCommentLength);
      })
      .then(function(fileComment) {
        return self._readRecord();
      });
    });
  };
  
  Parse.prototype._readEndOfCentralDirectoryRecord = function() {
    var self = this;
    self.pull(18).then(function(data) {
      
      var vars = binary.parse(data)
        .word16lu('diskNumber')
        .word16lu('diskStart')
        .word16lu('numberOfRecordsOnDisk')
        .word16lu('numberOfRecords')
        .word32lu('sizeOfCentralDirectory')
        .word32lu('offsetToStartOfCentralDirectory')
        .word16lu('commentLength')
        .vars;
  
      self.pull(vars.commentLength).then(function(comment) {
        comment = comment.toString('utf8');
        self.end();
        self.push(null);
      });
      
    });
  };
  
  Parse.prototype.promise = function() {
    var self = this;
    return new Promise(function(resolve,reject) {
      self.on('finish',resolve);
      self.on('error',reject);
    });
  };
  
  module.exports = Parse;