// Generated by CoffeeScript 2.7.0
(function() {
  var CombinedStream, DEFLATE_END, DeflateCRC32Stream, DeflatePartStream, ZIP_CD_DISK_START, ZIP_CD_EXTERNAL_ATT, ZIP_CD_FILE_COMM_LEN, ZIP_CD_INTERNAL_ATT, ZIP_CD_SIGNATURE, ZIP_CD_VERSION, ZIP_COMPRESSION_DEFLATE, ZIP_ECD_COMM_LEN, ZIP_ECD_DISK_NUM, ZIP_ECD_SIGNATURE, ZIP_ECD_SIZE, ZIP_ENTRY_EXTRAFIELD_LEN, ZIP_ENTRY_SIGNATURE, ZIP_FLAGS, ZIP_VERSION, centralDirectoryLength, crcUtils, create, createCDRecord, createEndOfCDRecord, createEntry, createFileHeader, dosFormatDate, dosFormatTime, fileHeaderLength, getCombinedCrc, iob, totalLength;

  crcUtils = require('@balena/node-crc-utils');

  CombinedStream = require('combined-stream');

  ({DeflateCRC32Stream} = require('crc32-stream'));

  // Zip constants, explained how they are used on each function.
  ZIP_VERSION = Buffer.from([0x0a, 0x00]);

  ZIP_FLAGS = Buffer.from([0x00, 0x00]);

  ZIP_ENTRY_SIGNATURE = Buffer.from([0x50, 0x4b, 0x03, 0x04]);

  ZIP_ENTRY_EXTRAFIELD_LEN = Buffer.from([0x00, 0x00]);

  ZIP_COMPRESSION_DEFLATE = Buffer.from([0x08, 0x00]);

  ZIP_CD_SIGNATURE = Buffer.from([0x50, 0x4b, 0x01, 0x02]);

  ZIP_CD_VERSION = Buffer.from([0x1e, 0x03]);

  ZIP_CD_FILE_COMM_LEN = Buffer.from([0x00, 0x00]);

  ZIP_CD_DISK_START = Buffer.from([0x00, 0x00]);

  ZIP_CD_INTERNAL_ATT = Buffer.from([0x01, 0x00]);

  ZIP_CD_EXTERNAL_ATT = Buffer.from([0x00, 0x00, 0xa4, 0x81]);

  ZIP_ECD_SIGNATURE = Buffer.from([0x50, 0x4b, 0x05, 0x06]);

  ZIP_ECD_DISK_NUM = Buffer.from([0x00, 0x00]);

  ZIP_ECD_COMM_LEN = Buffer.from([0x00, 0x00]);

  ZIP_ECD_SIZE = 22;

  // DEFLATE ending block
  DEFLATE_END = Buffer.from([0x03, 0x00]);

  // Use the logic briefly described here by the author of zlib library:
  // http://stackoverflow.com/questions/14744692/concatenate-multiple-zlib-compressed-data-streams-into-a-single-stream-efficient#comment51865187_14744792
  // to generate deflate streams that can be concatenated into a gzip stream
  DeflatePartStream = class DeflatePartStream extends DeflateCRC32Stream {
    constructor() {
      super(...arguments);
      this.buf = Buffer.alloc(0);
    }

    push(chunk) {
      if (chunk !== null) {
        // got another chunk, previous chunk is safe to send
        super.push(this.buf);
        return this.buf = chunk;
      } else {
        // got null signalling end of stream
        // inspect last chunk for 2-byte DEFLATE_END marker and remove it
        if (this.buf.length >= 2 && this.buf.slice(-2).equals(DEFLATE_END)) {
          this.buf = this.buf.slice(0, -2);
        }
        super.push(this.buf);
        return super.push(null);
      }
    }

    end() {
      return this.flush(() => {
        return super.end();
      });
    }

    metadata() {
      return {
        crc: this.digest().readUInt32BE(0),
        len: this.size(),
        zLen: this.size(true)
      };
    }

  };

  exports.createDeflatePart = function() {
    return new DeflatePartStream();
  };

  // Calculate length of file entry header
  // length of static information + filename length + extrafield length (0)
  fileHeaderLength = function(filename) {
    return 30 + filename.length;
  };

  // Calculate length of central directory (assumes only one file)
  // length of static information + filename length + central directory extrafield length (0)
  centralDirectoryLength = function(filename) {
    return 0x2e + filename.length;
  };

  // Return unsigned int as a buffer in little endian.
  // The size of the buffer needs to be passed as 2nd argument.
  iob = function(number, size) {
    var b;
    b = Buffer.alloc(size);
    b.fill(0).writeUIntLE(number, 0, size);
    return b;
  };

  // Create file entry header
  // Structure:
  //               2       4       6       8       10      12      14      16
  // 0000  | SIGNATURE     | VERS  | FLAGS | COMPM | MTIME | MDATE | CRC..
  // 0010    ..CRC | COMP_LEN      | RAW_LEN       | LNAME | LEXTR | FI..
  // 0020                       ...FILENAME (variable length)
  // 0030			     EXTRAFIELD (variable length)

  // Where:
  // 	SIGNATURE: Zip file entry signature (const)
  // 	VERSION: Zip version required
  // 	FLAGS: For us a constant 0000
  // 	COMPM: Compression method or 0 for no compression
  // 	MTIME / MDATE: Modification date
  // 	CRC: 32-bit crc checksum
  // 	COMP_LEN / RAW_LEN: Compressed and uncompressed length of contents
  // 	LNAME: Filename length
  // 	LEXTR: Length of extrafields attribute (last attribute)
  // 	FILENAME: Filename, ascii
  // 	EXTRAFIELD: Description of further custom properties (we use a constant)
  createFileHeader = function({filename, compressed_size, uncompressed_size, crc, mtime, mdate}) {
    return Buffer.concat([ZIP_ENTRY_SIGNATURE, ZIP_VERSION, ZIP_FLAGS, ZIP_COMPRESSION_DEFLATE, mtime, mdate, crc, iob(compressed_size, 4), iob(uncompressed_size, 4), iob(filename.length, 2), ZIP_ENTRY_EXTRAFIELD_LEN, Buffer.from(filename)]);
  };

  // Create central directory record, where each of the files in zip are listed (again)
  // Structure for each file entry:
  //               2       4       6       8       10      12      14      16
  // 0000  | CD SIGNATURE  | CDV   | VERS  | FLAGS | COMPM | MTIME | MDATE |
  // 0010  | CRC           | COMP_LEN      | RAW_LEN       | LNAME | LEXTR |
  // 0020  | LCOMM | #DISK | I ATT | EXTERNAL ATT  | FILE H OFFSET | FI...
  // 0020                       ...FILENAME (variable length)
  // 0030			     EXTRAFIELD (variable length)
  // 0040			     COMMENT (variable length, optional)

  // Where:
  // 	CD SIGNATURE: Zip central directory signature (const)
  // 	CDV: Version of central directory
  // 	VERS: Zip version required
  //	FLAGS: Extra flags, here a constant 0000
  //	COMPM, MTIME, MDATE, CRC, COMP_LEN, RAW_LEN, LNAME: Same as file header
  //	LCOMM: Length of file comment (always 0 here, no support for comments)
  //	#DISK: Disk number the files start (for multi-disk zip files, always 0 here)
  //	I ATT: File attributes used internally by the compressors and decompressors (const here)
  //	EXTERNAL ATT: Attributes useful for external applications (const here)
  //	FILE H OFFSET: Offset of file header from the start of zip file
  //	FILENAME: Filename, ascii
  //	EXTRAFIELD: Description of further custom properties (const here)
  //	COMMENT: File comment (none here, no support for comments)
  createCDRecord = function({filename, compressed_size, uncompressed_size, crc, mtime, mdate}, fileHeaderOffset) {
    return Buffer.concat([ZIP_CD_SIGNATURE, ZIP_CD_VERSION, ZIP_VERSION, ZIP_FLAGS, ZIP_COMPRESSION_DEFLATE, mtime, mdate, crc, iob(compressed_size, 4), iob(uncompressed_size, 4), iob(filename.length, 2), ZIP_ENTRY_EXTRAFIELD_LEN, ZIP_CD_FILE_COMM_LEN, ZIP_CD_DISK_START, ZIP_CD_INTERNAL_ATT, ZIP_CD_EXTERNAL_ATT, iob(fileHeaderOffset, 4), Buffer.from(filename)]);
  };

  // Create End of Central Directory Record
  // Structure:
  //               2       4       6       8       10      12      14      16
  // 0010  | ECD SIGNATURE | NDISK | #DISK | CDS   | FILES | CDSZ  | CDOFF |
  // 0020  | ECOMM |            COMMENT (variable length, optional)

  // Where:
  // 	ECD SIGNATURE: Signature of end of central directory record
  // 	NDISK: Number of disks the archive spans
  // 	#DISK: Disk central directory exists
  // 	CDS: Number of central directory entries
  // 	FILES: Total number of files in the archive
  // 	CDSZ: Size of central directory
  // 	CDOFF: Offset of central directory from disk it exists
  createEndOfCDRecord = function(entries) {
    var cd_offset, cd_size;
    cd_offset = entries.reduce((function(sum, x) {
      return sum + fileHeaderLength(x.filename) + x.compressed_size;
    }), 0);
    cd_size = entries.reduce((function(sum, x) {
      return sum + centralDirectoryLength(x.filename);
    }), 0);
    return Buffer.concat([ZIP_ECD_SIGNATURE, ZIP_ECD_DISK_NUM, ZIP_CD_DISK_START, iob(entries.length, 2), iob(entries.length, 2), iob(cd_size, 4), iob(cd_offset, 4), ZIP_ECD_COMM_LEN]);
  };

  dosFormatTime = function(d) {
    var buf;
    buf = Buffer.alloc(2);
    buf.writeUIntLE((d.getSeconds() / 2) + (d.getMinutes() << 5) + (d.getHours() << 11), 0, 2);
    return buf;
  };

  dosFormatDate = function(d) {
    var buf;
    buf = Buffer.alloc(2);
    buf.writeUIntLE(d.getDate() + ((d.getMonth() + 1) << 5) + ((d.getFullYear() - 1980) << 9), 0, 2);
    return buf;
  };

  getCombinedCrc = function(parts) {
    var buf;
    if (parts.length === 1) {
      // crc32 is stored as a number, has to be transformed to a Buffer
      buf = Buffer.alloc(4);
      buf.writeUInt32LE(parts[0].crc, 0, 4);
      return buf;
    } else {
      return crcUtils.crc32_combine_multi(parts).combinedCrc32;
    }
  };

  exports.totalLength = totalLength = function(entries) {
    return ZIP_ECD_SIZE + entries.reduce((function(sum, x) {
      return sum + x.zLen;
    }), 0);
  };

  exports.createEntry = createEntry = function(filename, parts, mdate) {
    var compressed_size, contentLength, entry, i, len, stream, uncompressed_size;
    if (mdate == null) {
      mdate = new Date();
    }
    compressed_size = parts.reduce((function(sum, x) {
      return sum + x.zLen;
    }), DEFLATE_END.length);
    uncompressed_size = parts.reduce((function(sum, x) {
      return sum + x.len;
    }), 0);
    contentLength = fileHeaderLength(filename) + compressed_size;
    entry = {
      filename: filename,
      compressed_size: compressed_size,
      uncompressed_size: uncompressed_size,
      crc: getCombinedCrc(parts),
      mtime: dosFormatTime(mdate),
      mdate: dosFormatDate(mdate),
      contentLength: contentLength,
      zLen: contentLength + centralDirectoryLength(filename),
      stream: CombinedStream.create()
    };
    entry.stream.append(createFileHeader(entry));
    for (i = 0, len = parts.length; i < len; i++) {
      ({stream} = parts[i]);
      entry.stream.append(stream);
    }
    entry.stream.append(DEFLATE_END);
    return entry;
  };

  exports.create = create = function(entries) {
    var entry, i, j, len, len1, offset, out;
    out = CombinedStream.create();
    for (i = 0, len = entries.length; i < len; i++) {
      entry = entries[i];
      out.append(entry.stream);
    }
    offset = 0;
    for (j = 0, len1 = entries.length; j < len1; j++) {
      entry = entries[j];
      out.append(createCDRecord(entry, offset));
      offset += entry.contentLength;
    }
    out.append(createEndOfCDRecord(entries));
    out.zLen = totalLength(entries);
    return out;
  };

  // DEPRECATED
  // Single-entry zip archive backwards-compatibility
  exports.createZip = function(filename, parts, mdate) {
    var entry;
    entry = createEntry(filename, parts, mdate);
    return create([entry]);
  };

}).call(this);
