"use strict";

Object.defineProperty(exports, "__esModule", {
    value: true
});
exports.SubStreamsInfo = exports.StartHeader = exports.SevenZFile = undefined;

var _bluebirdLst;

function _load_bluebirdLst() {
    return _bluebirdLst = require("bluebird-lst");
}

var _bluebirdLst2;

function _load_bluebirdLst2() {
    return _bluebirdLst2 = _interopRequireDefault(require("bluebird-lst"));
}

var _blockMapApi;

function _load_blockMapApi() {
    return _blockMapApi = require("builder-util-runtime/out/blockMapApi");
}

var _fsExtraP;

function _load_fsExtraP() {
    return _fsExtraP = require("fs-extra-p");
}

var _Archive;

function _load_Archive() {
    return _Archive = require("./Archive");
}

var _BitSet;

function _load_BitSet() {
    return _BitSet = require("./BitSet");
}

var _Folder;

function _load_Folder() {
    return _Folder = require("./Folder");
}

var _LittleEndianBuffer;

function _load_LittleEndianBuffer() {
    return _LittleEndianBuffer = require("./LittleEndianBuffer");
}

var _SevenZArchiveEntry;

function _load_SevenZArchiveEntry() {
    return _SevenZArchiveEntry = require("./SevenZArchiveEntry");
}

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

const sevenZSignature = Buffer.concat([Buffer.from("7z", "binary"), Buffer.from([0xBC, 0xAF, 0x27, 0x1C])]);
/* start header size */
const kEnd = 0x00;
const kArchiveProperties = 0x02;
const kAdditionalStreamsInfo = 0x03;
const kMainStreamsInfo = 0x04;
const kEncodedHeader = 0x17;
const kHeader = 0x01;
const kFilesInfo = 0x05;
const kPackInfo = 0x06;
const kUnpackInfo = 0x07;
const kSubStreamsInfo = 0x08;
const kSize = 0x09;
const kCRC = 0x0A;
const kFolder = 0x0B;
const kCodersUnpackSize = 0x0C;
const kNumUnpackStream = 0x0D;
const kEmptyStream = 0x0E;
const kEmptyFile = 0x0F;
const kAnti = 0x10;
const kName = 0x11;
const kCTime = 0x12;
const kATime = 0x13;
const kMTime = 0x14;
const kWinAttributes = 0x15;
const kStartPos = 0x18;
const kDummy = 0x19;
class SevenZFile {
    constructor(file) {
        this.file = file;
        this.fd = -1;
    }
    read() {
        var _this = this;

        return (0, (_bluebirdLst || _load_bluebirdLst()).coroutine)(function* () {
            _this.archive = yield _this.readHeaders();
            return _this.archive;
        })();
    }
    close() {
        if (this.fd !== -1) {
            const fd = this.fd;
            this.fd = -1;
            return (0, (_fsExtraP || _load_fsExtraP()).close)(fd);
        } else {
            return (_bluebirdLst2 || _load_bluebirdLst2()).default.resolve();
        }
    }
    // get nextEntry() {
    //   if (this.currentEntryIndex >= this.archive.files.length - 1) {
    //     return null
    //   }
    //   ++this.currentEntryIndex
    //   return this.archive.files[this.currentEntryIndex]
    // }
    readHeaders() {
        var _this2 = this;

        return (0, (_bluebirdLst || _load_bluebirdLst()).coroutine)(function* () {
            const fd = yield (0, (_fsExtraP || _load_fsExtraP()).open)(_this2.file, "r");
            _this2.fd = fd;
            let _buf = Buffer.allocUnsafe((_blockMapApi || _load_blockMapApi()).SIGNATURE_HEADER_SIZE);
            yield (0, (_fsExtraP || _load_fsExtraP()).read)(fd, _buf, 0, _buf.length, 0);
            const index = sevenZSignature.length;
            const signature = _buf.slice(0, index);
            if (!sevenZSignature.equals(signature)) {
                throw new Error("Bad 7z signature");
            }
            let buf = new (_LittleEndianBuffer || _load_LittleEndianBuffer()).LittleEndianBuffer(_buf, index);
            // 7zFormat.txt has it wrong - it's first major then minor
            const archiveVersionMajor = buf.readByte();
            const archiveVersionMinor = buf.readByte();
            if (archiveVersionMajor !== 0) {
                throw new Error(`Unsupported 7z version (${archiveVersionMajor},${archiveVersionMinor})`);
            }
            const startHeaderCrc = buf.readUnsignedInt();
            const startHeader = readStartHeader(startHeaderCrc, buf);
            _buf = Buffer.allocUnsafe(startHeader.nextHeaderSize);
            yield (0, (_fsExtraP || _load_fsExtraP()).read)(fd, _buf, 0, _buf.length, (_blockMapApi || _load_blockMapApi()).SIGNATURE_HEADER_SIZE + startHeader.nextHeaderOffset);
            // if (startHeader.nextHeaderCrc !== crc32(_buf)) {
            //   throw new Error("Header CRC mismatch")
            // }
            const archive = new (_Archive || _load_Archive()).Archive();
            archive.headerSize = startHeader.nextHeaderSize;
            buf = new (_LittleEndianBuffer || _load_LittleEndianBuffer()).LittleEndianBuffer(_buf);
            const nid = buf.readByte();
            if (nid === kEncodedHeader) {
                throw new Error("Encoded header is not supported");
            }
            if (nid === kHeader) {
                readHeader(buf, archive);
            } else {
                throw new Error("Broken or unsupported archive: no Header");
            }
            return archive;
        })();
    }
}
exports.SevenZFile = SevenZFile; // noinspection JSUnusedLocalSymbols

function readStartHeader(startHeaderCrc, buf) {
    // if (startHeaderCrc !== crc32(buf.slice())) {
    //   throw new Error("startHeader CRC mismatch")
    // }
    const nextHeaderOffset = buf.readLong();
    const nextHeaderSize = buf.readLong();
    const nextHeaderCrc = buf.readUnsignedInt();
    return new StartHeader(nextHeaderOffset, nextHeaderSize, nextHeaderCrc);
}
function readHeader(header, archive) {
    let nid = header.readUnsignedByte();
    if (nid === kArchiveProperties) {
        readArchiveProperties(header);
        nid = header.readUnsignedByte();
    }
    if (nid === kAdditionalStreamsInfo) {
        throw new Error("Additional streams unsupported");
    }
    if (nid === kMainStreamsInfo) {
        readStreamsInfo(header, archive);
        nid = header.readUnsignedByte();
    }
    if (nid === kFilesInfo) {
        readFilesInfo(header, archive);
        nid = header.readUnsignedByte();
    }
    if (nid !== kEnd) {
        throw new Error(`Badly terminated header, found ${nid}`);
    }
}
function readFilesInfo(header, archive) {
    const numFiles = readUint64(header);
    const files = new Array(numFiles);
    for (let i = 0; i < files.length; i++) {
        files[i] = new (_SevenZArchiveEntry || _load_SevenZArchiveEntry()).SevenZArchiveEntry();
    }
    let isEmptyStream = null;
    let isEmptyFile = null;
    let isAnti = null;
    while (true) {
        const propertyType = header.readUnsignedByte();
        if (propertyType === 0) {
            break;
        }
        const size = readUint64(header);
        switch (propertyType) {
            case kEmptyStream:
                {
                    isEmptyStream = readBits(header, files.length);
                    break;
                }
            case kEmptyFile:
                {
                    if (isEmptyStream == null) {
                        throw new Error("Header format error: kEmptyStream must appear before kEmptyFile");
                    }
                    isEmptyFile = readBits(header, isEmptyStream.cardinality());
                    break;
                }
            case kAnti:
                {
                    if (isEmptyStream == null) {
                        throw new Error("Header format error: kEmptyStream must appear before kAnti");
                    }
                    isAnti = readBits(header, isEmptyStream.cardinality());
                    break;
                }
            case kName:
                {
                    const external = header.readUnsignedByte();
                    if (external !== 0) {
                        throw new Error("Not implemented");
                    }
                    if ((size - 1 & 1) !== 0) {
                        throw new Error("File names length invalid");
                    }
                    const names = Buffer.allocUnsafe(size - 1);
                    header.get(names);
                    let nextFile = 0;
                    let nextName = 0;
                    for (let i = 0; i < names.length; i += 2) {
                        if (names[i] === 0 && names[i + 1] === 0) {
                            files[nextFile++].name = Buffer.from(names.slice(nextName, i)).toString("utf16le");
                            nextName = i + 2;
                        }
                    }
                    if (nextName !== names.length || nextFile !== files.length) {
                        throw new Error("Error parsing file names");
                    }
                    break;
                }
            case kCTime:
                {
                    throw new Error("Must be no kCTime");
                }
            case kATime:
                {
                    throw new Error("Must be no kATime");
                }
            case kMTime:
                {
                    throw new Error("Must be no kMTime");
                }
            case kWinAttributes:
                {
                    const attributesDefined = readAllOrBits(header, files.length);
                    const external = header.readUnsignedByte();
                    if (external !== 0) {
                        throw new Error("Unimplemented");
                    }
                    for (let i = 0; i < files.length; i++) {
                        files[i].hasWindowsAttributes = attributesDefined.get(i);
                        if (files[i].hasWindowsAttributes) {
                            files[i].windowsAttributes = header.readInt();
                        }
                    }
                    break;
                }
            case kStartPos:
                {
                    throw new Error("kStartPos is unsupported");
                }
            case kDummy:
                {
                    if (skipBytesFully(header, size) < size) {
                        throw new Error("Incomplete kDummy property");
                    }
                    break;
                }
            default:
                {
                    if (skipBytesFully(header, size) < size) {
                        throw new Error(`Incomplete property of type ${propertyType}`);
                    }
                    break;
                }
        }
    }
    let nonEmptyFileCounter = 0;
    let emptyFileCounter = 0;
    for (let i = 0; i < files.length; i++) {
        files[i].hasStream = isEmptyStream == null || !isEmptyStream.get(i);
        if (files[i].hasStream) {
            files[i].isDirectory = false;
            files[i].isAntiItem = false;
            files[i].hasCrc = archive.subStreamsInfo.hasCrc.get(nonEmptyFileCounter);
            files[i].crcValue = archive.subStreamsInfo.crcs[nonEmptyFileCounter];
            files[i].size = archive.subStreamsInfo.unpackSizes[nonEmptyFileCounter];
            ++nonEmptyFileCounter;
        } else {
            files[i].isDirectory = isEmptyFile == null || !isEmptyFile.get(emptyFileCounter);
            files[i].isAntiItem = isAnti != null && isAnti.get(emptyFileCounter);
            files[i].hasCrc = false;
            files[i].size = 0;
            ++emptyFileCounter;
        }
    }
    archive.files = files;
    calculateStreamMap(archive);
}
function calculateStreamMap(archive) {
    const streamMap = new (_Archive || _load_Archive()).StreamMap();
    let nextFolderPackStreamIndex = 0;
    const numFolders = archive.folders != null ? archive.folders.length : 0;
    for (let i = 0; i < numFolders; i++) {
        const folder = archive.folders[i];
        folder.firstPackedStreamIndex = nextFolderPackStreamIndex;
        nextFolderPackStreamIndex += folder.packedStreams.length;
    }
    let nextPackStreamOffset = 0;
    const numPackSizes = archive.packedSizes != null ? archive.packedSizes.length : 0;
    streamMap.packStreamOffsets = new Array(numPackSizes);
    for (let i = 0; i < numPackSizes; i++) {
        streamMap.packStreamOffsets[i] = nextPackStreamOffset;
        nextPackStreamOffset += archive.packedSizes[i];
    }
    streamMap.folderFirstFileIndex = new Array(numFolders);
    let nextFolderIndex = 0;
    let nextFolderUnpackStreamIndex = 0;
    for (let i = 0; i < archive.files.length; i++) {
        const file = archive.files[i];
        if (!file.hasStream && nextFolderUnpackStreamIndex === 0) {
            file.blockIndex = -1;
            continue;
        }
        if (nextFolderUnpackStreamIndex === 0) {
            for (; nextFolderIndex < archive.folders.length; ++nextFolderIndex) {
                streamMap.folderFirstFileIndex[nextFolderIndex] = i;
                const folder = archive.folders[nextFolderIndex];
                if (folder.numUnpackSubStreams > 0) {
                    break;
                }
            }
            if (nextFolderIndex >= archive.folders.length) {
                throw new Error("Too few folders in archive");
            }
        }
        file.blockIndex = nextFolderIndex;
        if (!file.hasStream) {
            continue;
        }
        ++nextFolderUnpackStreamIndex;
        if (nextFolderUnpackStreamIndex >= archive.folders[nextFolderIndex].numUnpackSubStreams) {
            ++nextFolderIndex;
            nextFolderUnpackStreamIndex = 0;
        }
    }
    archive.streamMap = streamMap;
}
function skipBytesFully(input, bytesToSkip) {
    if (bytesToSkip < 1) {
        return 0;
    }
    const maxSkip = input.remaining();
    if (maxSkip < bytesToSkip) {
        bytesToSkip = maxSkip;
    }
    input.skip(bytesToSkip);
    return bytesToSkip;
}
function readArchiveProperties(input) {
    // just skip it
    let nid = input.readUnsignedByte();
    while (nid !== kEnd) {
        input.skip(readUint64(input));
        nid = input.readUnsignedByte();
    }
}
function readStreamsInfo(header, archive) {
    let nid = header.readUnsignedByte();
    if (nid === kPackInfo) {
        readPackInfo(header, archive);
        nid = header.readUnsignedByte();
    }
    if (nid === kUnpackInfo) {
        readUnpackInfo(header, archive);
        nid = header.readUnsignedByte();
    } else {
        // archive without unpack/coders info
        archive.folders = [];
    }
    if (nid === kSubStreamsInfo) {
        readSubStreamsInfo(header, archive);
        nid = header.readUnsignedByte();
    }
    if (nid !== kEnd) {
        throw new Error("Badly terminated StreamsInfo");
    }
}
function readSubStreamsInfo(header, archive) {
    for (const folder of archive.folders) {
        folder.numUnpackSubStreams = 1;
    }
    let totalUnpackStreams = archive.folders.length;
    let nid = header.readUnsignedByte();
    if (nid === kNumUnpackStream) {
        totalUnpackStreams = 0;
        for (const folder of archive.folders) {
            const numStreams = readUint64(header);
            folder.numUnpackSubStreams = numStreams;
            totalUnpackStreams += numStreams;
        }
        nid = header.readUnsignedByte();
    }
    const subStreamsInfo = new SubStreamsInfo();
    subStreamsInfo.unpackSizes = new Array(totalUnpackStreams);
    subStreamsInfo.hasCrc = new (_BitSet || _load_BitSet()).BitSet(totalUnpackStreams);
    subStreamsInfo.crcs = new Array(totalUnpackStreams);
    let nextUnpackStream = 0;
    for (const folder of archive.folders) {
        if (folder.numUnpackSubStreams === 0) {
            continue;
        }
        let sum = 0;
        if (nid === kSize) {
            for (let i = 0; i < folder.numUnpackSubStreams - 1; i++) {
                const size = readUint64(header);
                subStreamsInfo.unpackSizes[nextUnpackStream++] = size;
                sum += size;
            }
        }
        subStreamsInfo.unpackSizes[nextUnpackStream++] = folder.getUnpackSize() - sum;
    }
    if (nid === kSize) {
        nid = header.readUnsignedByte();
    }
    let numDigests = 0;
    for (const folder of archive.folders) {
        if (folder.numUnpackSubStreams !== 1 || !folder.hasCrc) {
            numDigests += folder.numUnpackSubStreams;
        }
    }
    if (nid === kCRC) {
        const hasMissingCrc = readAllOrBits(header, numDigests);
        const missingCrcs = new Array(numDigests);
        for (let i = 0; i < numDigests; i++) {
            if (hasMissingCrc.get(i)) {
                missingCrcs[i] = header.readUnsignedInt();
            }
        }
        let nextCrc = 0;
        let nextMissingCrc = 0;
        for (const folder of archive.folders) {
            if (folder.numUnpackSubStreams === 1 && folder.hasCrc) {
                subStreamsInfo.hasCrc.set(nextCrc);
                subStreamsInfo.crcs[nextCrc] = folder.crc;
                ++nextCrc;
            } else {
                for (let i = 0; i < folder.numUnpackSubStreams; i++) {
                    if (hasMissingCrc.get(nextMissingCrc)) {
                        subStreamsInfo.hasCrc.set(nextCrc);
                    } else {
                        subStreamsInfo.hasCrc.clear(nextCrc);
                    }
                    subStreamsInfo.crcs[nextCrc] = missingCrcs[nextMissingCrc];
                    ++nextCrc;
                    ++nextMissingCrc;
                }
            }
        }
        nid = header.readUnsignedByte();
    }
    if (nid !== kEnd) {
        throw new Error("Badly terminated SubStreamsInfo");
    }
    archive.subStreamsInfo = subStreamsInfo;
}
function readUnpackInfo(header, archive) {
    let nid = header.readUnsignedByte();
    if (nid !== kFolder) {
        throw new Error(`Expected kFolder, got ${nid}`);
    }
    const numFolders = readUint64(header);
    const folders = new Array(numFolders);
    archive.folders = folders;
    const external = header.readUnsignedByte();
    if (external !== 0) {
        throw new Error("External unsupported");
    }
    for (let i = 0; i < numFolders; i++) {
        folders[i] = readFolder(header);
    }
    nid = header.readUnsignedByte();
    if (nid !== kCodersUnpackSize) {
        throw new Error(`Expected kCodersUnpackSize, got ${nid}`);
    }
    for (const folder of folders) {
        folder.unpackSizes = new Array(folder.totalOutputStreams);
        for (let i = 0; i < folder.totalOutputStreams; i++) {
            folder.unpackSizes[i] = readUint64(header);
        }
    }
    nid = header.readUnsignedByte();
    if (nid === kCRC) {
        const crcsDefined = readAllOrBits(header, numFolders);
        for (let i = 0; i < numFolders; i++) {
            if (crcsDefined.get(i)) {
                folders[i].hasCrc = true;
                folders[i].crc = 0xffffFFFF & header.readInt();
            } else {
                folders[i].hasCrc = false;
            }
        }
        nid = header.readUnsignedByte();
    }
    if (nid !== kEnd) {
        throw new Error("Badly terminated UnpackInfo");
    }
}
function readFolder(header) {
    const folder = new (_Folder || _load_Folder()).Folder();
    const numCoders = readUint64(header);
    const coders = new Array(numCoders);
    let totalInStreams = 0;
    let totalOutStreams = 0;
    for (let i = 0; i < coders.length; i++) {
        coders[i] = new (_Folder || _load_Folder()).Coder();
        const bits = header.readUnsignedByte();
        const idSize = bits & 0xf;
        const isSimple = (bits & 0x10) === 0;
        const hasAttributes = (bits & 0x20) !== 0;
        const moreAlternativeMethods = (bits & 0x80) !== 0;
        coders[i].decompressionMethodId = Buffer.allocUnsafe(idSize);
        header.get(coders[i].decompressionMethodId);
        if (isSimple) {
            coders[i].numInStreams = 1;
            coders[i].numOutStreams = 1;
        } else {
            coders[i].numInStreams = readUint64(header);
            coders[i].numOutStreams = readUint64(header);
        }
        totalInStreams += coders[i].numInStreams;
        totalOutStreams += coders[i].numOutStreams;
        if (hasAttributes) {
            const propertiesSize = readUint64(header);
            coders[i].properties = Buffer.allocUnsafe(propertiesSize);
            header.get(coders[i].properties);
        }
        // would need to keep looping as above:
        // noinspection LoopStatementThatDoesntLoopJS
        while (moreAlternativeMethods) {
            throw new Error("Alternative methods are unsupported, please report. The reference implementation doesn't support them either.");
        }
    }
    folder.coders = coders;
    folder.totalInputStreams = totalInStreams;
    folder.totalOutputStreams = totalOutStreams;
    if (totalOutStreams === 0) {
        throw new Error("Total output streams can't be 0");
    }
    const numBindPairs = totalOutStreams - 1;
    const bindPairs = new Array(numBindPairs);
    for (let i = 0; i < numBindPairs; i++) {
        bindPairs[i] = new (_Folder || _load_Folder()).BindPair();
        bindPairs[i].inIndex = readUint64(header);
        bindPairs[i].outIndex = readUint64(header);
    }
    folder.bindPairs = bindPairs;
    if (totalInStreams < numBindPairs) {
        throw new Error("Total input streams can't be less than the number of bind pairs");
    }
    const numPackedStreams = totalInStreams - numBindPairs;
    const packedStreams = new Array(numPackedStreams);
    if (numPackedStreams === 1) {
        let i;
        for (i = 0; i < totalInStreams; i++) {
            if (folder.findBindPairForInStream(i) < 0) {
                break;
            }
        }
        if (i === totalInStreams) {
            throw new Error("Couldn't find stream's bind pair index");
        }
        packedStreams[0] = i;
    } else if (numPackedStreams > 1) {
        for (let i = 0; i < numPackedStreams; i++) {
            packedStreams[i] = readUint64(header);
        }
    }
    folder.packedStreams = packedStreams;
    return folder;
}
function readPackInfo(header, archive) {
    archive.packPosition = readUint64(header);
    const numPackStreams = readUint64(header);
    let nid = header.readUnsignedByte();
    if (nid === kSize) {
        archive.packedSizes = new Array(numPackStreams);
        for (let i = 0; i < archive.packedSizes.length; i++) {
            archive.packedSizes[i] = readUint64(header);
        }
        nid = header.readUnsignedByte();
    }
    if (nid === kCRC) {
        // just read and ignore crc
        const packedCrcsDefined = readAllOrBits(header, numPackStreams);
        for (let i = 0; i < numPackStreams; i++) {
            if (packedCrcsDefined.get(i)) {
                header.skip(4);
            }
        }
        nid = header.readUnsignedByte();
    }
    if (nid !== kEnd) {
        throw new Error(`Badly terminated PackInfo (${nid})`);
    }
}
function readAllOrBits(header, size) {
    const areAllDefined = header.readUnsignedByte();
    let bits;
    if (areAllDefined !== 0) {
        bits = new (_BitSet || _load_BitSet()).BitSet(size);
        for (let i = 0; i < size; i++) {
            bits.set(i);
        }
    } else {
        bits = readBits(header, size);
    }
    return bits;
}
function readBits(header, size) {
    const bits = new (_BitSet || _load_BitSet()).BitSet(size);
    let mask = 0;
    let cache = 0;
    for (let i = 0; i < size; i++) {
        if (mask === 0) {
            mask = 0x80;
            cache = header.readUnsignedByte();
        }
        if ((cache & mask) !== 0) {
            bits.set(i);
        } else {
            bits.clear(i);
        }
        mask >>>= 1;
    }
    return bits;
}
function readUint64(input) {
    // long rather than int as it might get shifted beyond the range of an int
    const firstByte = input.readUnsignedByte();
    let mask = 0x80;
    let value = 0;
    for (let i = 0; i < 8; i++) {
        if ((firstByte & mask) === 0) {
            return value | (firstByte & mask - 1) << 8 * i;
        }
        const nextByte = input.readUnsignedByte();
        value |= nextByte << 8 * i;
        mask >>>= 1;
    }
    return value;
}
class StartHeader {
    // noinspection JSUnusedGlobalSymbols
    constructor(nextHeaderOffset, nextHeaderSize, nextHeaderCrc) {
        this.nextHeaderOffset = nextHeaderOffset;
        this.nextHeaderSize = nextHeaderSize;
        this.nextHeaderCrc = nextHeaderCrc;
    }
}
exports.StartHeader = StartHeader;
class SubStreamsInfo {}
exports.SubStreamsInfo = SubStreamsInfo; //# sourceMappingURL=SevenZFile.js.map