/*
 * SDK Pullenti Unitext, version 4.33, fabruary 2026. Copyright (c) 2013-2026, Pullenti. All rights reserved. 
 * Non-Commercial Freeware and Commercial Software. 
 * This class is generated using the converter UniSharping (www.unisharping.ru) from Pullenti C# project. 
 * The latest version of the code is available on the site www.pullenti.ru
 */

const Utils = require("./../../../unisharp/Utils");
const Stream = require("./../../../unisharp/Stream");

const CompressionMethod = require("./CompressionMethod");
const GeneralBitFlags = require("./GeneralBitFlags");
const InflaterInputStream = require("./InflaterInputStream");
const ZipConstants = require("./ZipConstants");
const Inflater = require("./Inflater");
const Crc32 = require("./Crc32");
const ZipEntry = require("./ZipEntry");

// This is an InflaterInputStream that reads the files baseInputStream an zip archive
// one after another.  It has a special method to get the zip entry of
// the next file.  The zip entry contains information about the file name
// size, compressed size, Crc, etc.
// It includes support for Stored and Deflated entries.
class ZipInputStream extends InflaterInputStream {
    
    constructor(_baseInputStream, bufferSize = 4096) {
        super(_baseInputStream, new Inflater(true), bufferSize);
        this.internalReader = null;
        this.m_Crc = new Crc32();
        this.m_Entry = null;
        this.m_Size = 0;
        this.m_Method = 0;
        this.m_Flags = 0;
        this.m_Password = null;
        this._ReadDataHandler_ReadingNotAvailable = new ZipInputStream.ReadDataHandler_ReadingNotAvailable(this);
        this._ReadDataHandler_InitialRead = new ZipInputStream.ReadDataHandler_InitialRead(this);
        this._ReadDataHandler_ReadingNotSupported = new ZipInputStream.ReadDataHandler_ReadingNotSupported(this);
        this._ReadDataHandler_BodyRead = new ZipInputStream.ReadDataHandler_BodyRead(this);
        this.internalReader = this._ReadDataHandler_ReadingNotAvailable;
    }
    
    get password() {
        return this.m_Password;
    }
    set password(value) {
        this.m_Password = value;
        return value;
    }
    
    get canDecompressEntry() {
        return (this.m_Entry !== null) && this.m_Entry.canDecompress;
    }
    
    getNextEntry() {
        if (this.m_Crc === null) 
            throw new Error("Closed.");
        if (this.m_Entry !== null) 
            this.closeEntry();
        let header = this.inputBuffer.readLeInt();
        if ((header === ZipConstants.CENTRAL_HEADER_SIGNATURE || header === ZipConstants.END_OF_CENTRAL_DIRECTORY_SIGNATURE || header === ZipConstants.CENTRAL_HEADER_DIGITAL_SIGNATURE) || header === ZipConstants.ARCHIVE_EXTRA_DATA_SIGNATURE || header === ZipConstants.ZIP64CENTRAL_FILE_HEADER_SIGNATURE) {
            this.close();
            return null;
        }
        if ((header === ZipConstants.SPANNING_TEMP_SIGNATURE) || (header === ZipConstants.SPANNING_SIGNATURE)) 
            header = this.inputBuffer.readLeInt();
        if (header !== ZipConstants.LOCAL_HEADER_SIGNATURE) 
            throw new Error("Wrong Local header signature: 0x" + ((header).toString(16).toUpperCase()));
        let versionRequiredToExtract = this.inputBuffer.readLeShort();
        this.m_Flags = this.inputBuffer.readLeShort();
        this.m_Method = this.inputBuffer.readLeShort();
        let dostime = this.inputBuffer.readLeInt();
        let crc2 = this.inputBuffer.readLeInt();
        this.csize = this.inputBuffer.readLeInt();
        this.m_Size = this.inputBuffer.readLeInt();
        let nameLen = this.inputBuffer.readLeShort();
        let extraLen = this.inputBuffer.readLeShort();
        let isCrypted = ((this.m_Flags & 1)) === 1;
        let buffer = new Uint8Array(nameLen);
        this.inputBuffer.readRawBuffer(buffer);
        let name = ZipConstants.convertToStringExt0(this.m_Flags, buffer);
        this.m_Entry = new ZipEntry(name, versionRequiredToExtract);
        this.m_Entry.flags = this.m_Flags;
        this.m_Entry._CompressionMethod = CompressionMethod.of(this.m_Method);
        if (((this.m_Flags & 8)) === 0) {
            this.m_Entry.crc = crc2;
            this.m_Entry.size = this.m_Size;
            this.m_Entry.compressedSize = this.csize;
            this.m_Entry.cryptoCheckValue = (((crc2 >> 24)) & 0xff);
        }
        else {
            if (crc2 !== 0) 
                this.m_Entry.crc = crc2;
            if (this.m_Size !== 0) 
                this.m_Entry.size = this.m_Size;
            if (this.csize !== 0) 
                this.m_Entry.compressedSize = this.csize;
            this.m_Entry.cryptoCheckValue = ((((dostime) >>> 8)) & 0xff);
        }
        this.m_Entry.dosTime = dostime;
        if (extraLen > 0) {
            let extra = new Uint8Array(extraLen);
            this.inputBuffer.readRawBuffer(extra);
            this.m_Entry.extraData = extra;
        }
        this.m_Entry.processExtraData(true);
        if (this.m_Entry.compressedSize >= 0) 
            this.csize = this.m_Entry.compressedSize;
        if (this.m_Entry.size >= 0) 
            this.m_Size = this.m_Entry.size;
        if (this.m_Method === (CompressionMethod.STORED.value()) && (((!isCrypted && this.csize !== this.m_Size) || ((isCrypted && (this.csize - ZipConstants.CRYPTO_HEADER_SIZE) !== this.m_Size))))) 
            throw new Error("Stored, but compressed != uncompressed");
        if (this.m_Entry.isCompressionMethodSupported()) 
            this.internalReader = this._ReadDataHandler_InitialRead;
        else 
            this.internalReader = this._ReadDataHandler_ReadingNotSupported;
        return this.m_Entry;
    }
    
    readDataDescriptor() {
        if (this.inputBuffer.readLeInt() !== ZipConstants.DATA_DESCRIPTOR_SIGNATURE) 
            throw new Error("Data descriptor signature not found");
        this.m_Entry.crc = this.inputBuffer.readLeInt();
        if (this.m_Entry.localHeaderRequiresZip64) {
            this.csize = this.inputBuffer.readLeInt();
            this.inputBuffer.readLeInt();
            this.m_Size = this.inputBuffer.readLeInt();
            this.inputBuffer.readLeInt();
        }
        else {
            this.csize = this.inputBuffer.readLeInt();
            this.m_Size = this.inputBuffer.readLeInt();
        }
        this.m_Entry.compressedSize = this.csize;
        this.m_Entry.size = this.m_Size;
    }
    
    completeCloseEntry(testCrc) {
        if (((this.m_Flags & 8)) !== 0) 
            this.readDataDescriptor();
        this.m_Size = 0;
        if (testCrc && (Utils.compareUnsigned(this.m_Crc.value, this.m_Entry.crc) != 0)) 
            throw new Error("CRC mismatch");
        this.m_Crc.reset();
        if (this.m_Method === (CompressionMethod.DEFLATED.value())) 
            this.inf.reset();
        this.m_Entry = null;
    }
    
    closeEntry() {
        if (this.m_Crc === null) 
            throw new Error("Closed");
        if (this.m_Entry === null) 
            return;
        if (this.m_Method === (CompressionMethod.DEFLATED.value())) {
            if (((this.m_Flags & 8)) !== 0) {
                let tmp = new Uint8Array(4096);
                while (this.read(tmp, 0, tmp.length) > 0) {
                }
                return;
            }
            this.csize -= this.inf.totalIn;
            this.inputBuffer.available = this.inputBuffer.available + this.inf.remainingInput;
        }
        if ((this.inputBuffer.available > this.csize) && (this.csize >= 0)) 
            this.inputBuffer.available = ((this.inputBuffer.available) - (this.csize));
        else {
            this.csize -= this.inputBuffer.available;
            this.inputBuffer.available = 0;
            while (this.csize !== 0) {
                let skipped = super.skip(this.csize);
                if (skipped <= 0) 
                    throw new Error("Zip archive ends early.");
                this.csize -= skipped;
            }
        }
        this.completeCloseEntry(false);
    }
    
    get available() {
        return (this.m_Entry !== null ? 1 : 0);
    }
    
    get length() {
        if (this.m_Entry !== null) {
            if (this.m_Entry.size >= 0) 
                return this.m_Entry.size;
            else 
                return 0;
        }
        else 
            return 0;
    }
    
    readByte() {
        let b = new Uint8Array(1);
        if (this.read(b, 0, 1) <= 0) 
            return -1;
        return (b[0]) & 0xff;
    }
    
    readingNotAvailable(destination, offset, count) {
        throw new Error("Unable to read from this stream");
    }
    
    readingNotSupported(destination, offset, count) {
        throw new Error("The compression method for this entry is not supported");
    }
    
    initialRead(destination, offset, count) {
        if (!this.canDecompressEntry) 
            throw new Error("Library cannot extract this entry. Version required is (" + this.m_Entry.version.toString() + ")");
        if ((this.csize > 0) || (((this.m_Flags & (GeneralBitFlags.DESCRIPTOR.value()))) !== 0)) {
            if ((this.m_Method === (CompressionMethod.DEFLATED.value())) && (this.inputBuffer.available > 0)) 
                this.inputBuffer.setInflaterInput(this.inf);
            this.internalReader = this._ReadDataHandler_BodyRead;
            return this.bodyRead(destination, offset, count);
        }
        else {
            this.internalReader = this._ReadDataHandler_ReadingNotAvailable;
            return 0;
        }
    }
    
    read(buffer, offset, count) {
        if (buffer === null) 
            throw new Error("null buffer");
        if (offset < 0) 
            throw new Error("bad offset");
        if (count < 0) 
            throw new Error("bad count");
        if (((buffer.length - offset)) < count) 
            throw new Error("Invalid offset/count combination");
        try {
            return this.internalReader.call(buffer, offset, count);
        } catch (ex) {
            throw new Error(ex.message, ex);
        }
    }
    
    bodyRead(buffer, offset, count) {
        if (this.m_Crc === null) 
            throw new Error("Closed");
        if ((this.m_Entry === null) || (count <= 0)) 
            return 0;
        if ((offset + count) > buffer.length) 
            throw new Error("Offset + count exceeds buffer size");
        let finished = false;
        switch (this.m_Method) { 
        case CompressionMethod.DEFLATED.value(): {
            count = super.read(buffer, offset, count);
            if (count <= 0) {
                if (!this.inf.isFinished) 
                    throw new Error("Inflater not finished!");
                this.inputBuffer.available = this.inf.remainingInput;
                if (((this.m_Flags & 8)) === 0 && (((this.inf.totalIn !== this.csize && this.csize !== 0xFFFFFFFF && this.csize !== -1) || this.inf.totalOut !== this.m_Size))) 
                    throw new Error(((("Size mismatch: " + (String(this.csize)) + ";") + (String(this.m_Size)) + " <-> ") + (String(this.inf.totalIn)) + ";") + (String(this.inf.totalOut)));
                this.inf.reset();
                finished = true;
            }
            break;
        }
        case CompressionMethod.STORED.value(): {
            if ((count > this.csize) && (this.csize >= 0)) 
                count = this.csize;
            if (count > 0) {
                count = this.inputBuffer.readClearTextBuffer(buffer, offset, count);
                if (count > 0) {
                    this.csize -= count;
                    this.m_Size -= count;
                }
            }
            if (this.csize === 0) 
                finished = true;
            else if (count < 0) 
                throw new Error("EOF in stored block");
            break;
        }
        }
        if (count > 0) 
            this.m_Crc.updateByBufEx(buffer, offset, count);
        if (finished) 
            this.completeCloseEntry(true);
        return count;
    }
    
    close() {
        this.internalReader = this._ReadDataHandler_ReadingNotAvailable;
        this.m_Crc = null;
        this.m_Entry = null;
        super.close();
    }
}


/**
 * Delegate for reading bytes from a stream.
 */
ZipInputStream.ReadDataHandler = class  {
    
    call(b, offset, _length) {
        return null;
    }
}


ZipInputStream.ReadDataHandler_ReadingNotAvailable = class  extends ZipInputStream.ReadDataHandler {
    
    constructor(src) {
        super();
        this.m_Source = null;
        this.m_Source = src;
    }
    
    call(destination, offset, count) {
        return this.m_Source.readingNotAvailable(destination, offset, count);
    }
}


ZipInputStream.ReadDataHandler_InitialRead = class  extends ZipInputStream.ReadDataHandler {
    
    constructor(src) {
        super();
        this.m_Source = null;
        this.m_Source = src;
    }
    
    call(destination, offset, count) {
        return this.m_Source.initialRead(destination, offset, count);
    }
}


ZipInputStream.ReadDataHandler_ReadingNotSupported = class  extends ZipInputStream.ReadDataHandler {
    
    constructor(src) {
        super();
        this.m_Source = null;
        this.m_Source = src;
    }
    
    call(destination, offset, count) {
        return this.m_Source.readingNotSupported(destination, offset, count);
    }
}


ZipInputStream.ReadDataHandler_BodyRead = class  extends ZipInputStream.ReadDataHandler {
    
    constructor(src) {
        super();
        this.m_Source = null;
        this.m_Source = src;
    }
    
    call(buffer, offset, count) {
        return this.m_Source.bodyRead(buffer, offset, count);
    }
}


module.exports = ZipInputStream