/*
 * 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 GeneralBitFlags = require("./GeneralBitFlags");
const HostSystemID = require("./HostSystemID");
const ZipExtraData = require("./ZipExtraData");
const CompressionMethod = require("./CompressionMethod");
const ZipConstants = require("./ZipConstants");
const ZipEntryKnown = require("./ZipEntryKnown");

// This class represents an entry in a zip archive.  This can be a file
// or a directory
// ZipFile and ZipInputStream will give you instances of this class as
// information about the members in an archive.  ZipOutputStream
// uses an instance of this class when creating an entry in a Zip file.
class ZipEntry {
    
    constructor(_name, versionRequiredToExtract = 0, madeByInfo = ZipConstants.VERSION_MADE_BY, method = CompressionMethod.DEFLATED) {
        this.m_Known = ZipEntryKnown.NONE;
        this.m_ExternalFileAttributes = -1;
        this.m_VersionMadeBy = 0;
        this.m_Name = null;
        this.m_Size = 0;
        this.m_CompressedSize = 0;
        this.versionToExtract = 0;
        this.m_Crc = 0;
        this.m_DosTime = 0;
        this.m_Method = CompressionMethod.DEFLATED;
        this.m_Extra = null;
        this.m_Comment = null;
        this.flags = 0;
        this.m_ZipFileIndex = -1;
        this.m_Ooffset = 0;
        this.forceZip64_ = false;
        this.m_CryptoCheckValue = 0;
        this._aesVer = 0;
        this._aesEncryptionStrength = 0;
        if (_name === null) 
            throw new Error("name");
        if (_name.length > 0xffff) 
            throw new Error("Name is too long", "name");
        if ((versionRequiredToExtract !== 0) && ((versionRequiredToExtract < 10))) 
            throw new Error("versionRequiredToExtract");
        this.dateTime = Utils.now();
        this.m_Name = _name;
        this.m_VersionMadeBy = madeByInfo;
        this.versionToExtract = versionRequiredToExtract;
        this.m_Method = method;
    }
    
    get hasCrc() {
        return ((this.m_Known.value()) & (ZipEntryKnown.CRC.value())) !== (0);
    }
    
    get isCrypted() {
        return ((this.flags & 1)) !== 0;
    }
    set isCrypted(value) {
        if (value) 
            this.flags |= 1;
        else 
            this.flags &= (~1);
        return value;
    }
    
    get isUnicodeText() {
        return ((this.flags & (GeneralBitFlags.UNICODETEXT.value()))) !== 0;
    }
    set isUnicodeText(value) {
        if (value) 
            this.flags |= (GeneralBitFlags.UNICODETEXT.value());
        else 
            this.flags &= (~(GeneralBitFlags.UNICODETEXT.value()));
        return value;
    }
    
    get cryptoCheckValue() {
        return this.m_CryptoCheckValue;
    }
    set cryptoCheckValue(value) {
        this.m_CryptoCheckValue = value;
        return value;
    }
    
    get zipFileIndex() {
        return this.m_ZipFileIndex;
    }
    set zipFileIndex(value) {
        this.m_ZipFileIndex = value;
        return value;
    }
    
    get offset() {
        return this.m_Ooffset;
    }
    set offset(value) {
        this.m_Ooffset = value;
        return value;
    }
    
    get externalFileAttributes() {
        if (((this.m_Known.value()) & (ZipEntryKnown.EXTERNALATTRIBUTES.value())) === (0)) 
            return -1;
        else 
            return this.m_ExternalFileAttributes;
    }
    set externalFileAttributes(value) {
        this.m_ExternalFileAttributes = value;
        this.m_Known = ZipEntryKnown.of((this.m_Known.value()) | (ZipEntryKnown.EXTERNALATTRIBUTES.value()));
        return value;
    }
    
    get versionMadeBy() {
        return ((this.m_VersionMadeBy) & 0xff);
    }
    
    get isDOSEntry() {
        return ((this.hostSystem === (HostSystemID.MSDOS.value())) || (this.hostSystem === (HostSystemID.WINDOWSNT.value())));
    }
    
    hasDosAttributes(attributes) {
        let result = false;
        if (((this.m_Known.value()) & (ZipEntryKnown.EXTERNALATTRIBUTES.value())) !== (0)) {
            if ((((this.hostSystem === (HostSystemID.MSDOS.value())) || (this.hostSystem === (HostSystemID.WINDOWSNT.value())))) && ((this.externalFileAttributes & attributes)) === attributes) 
                result = true;
        }
        return result;
    }
    
    get hostSystem() {
        return (((this.m_VersionMadeBy) >>> 8)) & 0xff;
    }
    set hostSystem(value) {
        this.m_VersionMadeBy &= (0xff);
        this.m_VersionMadeBy |= (((value & 0xff)) << 8);
        return value;
    }
    
    get version() {
        if (Utils.compareUnsigned(this.versionToExtract, 0) != 0) 
            return this.versionToExtract;
        else {
            let result = 10;
            if (this.aESKeySize > 0) 
                result = ZipConstants.VERSION_AES;
            else if (this.centralHeaderRequiresZip64) 
                result = ZipConstants.VERSION_ZIP64;
            else if (CompressionMethod.DEFLATED === this.m_Method) 
                result = 20;
            else if (this.isDirectory === true) 
                result = 20;
            else if (this.isCrypted === true) 
                result = 20;
            else if (this.hasDosAttributes(0x08)) 
                result = 11;
            return result;
        }
    }
    
    get canDecompress() {
        return (this.version <= ZipConstants.VERSION_MADE_BY) && ((((this.version === 10) || (this.version === 11) || (this.version === 20)) || (this.version === 45) || (this.version === 51))) && this.isCompressionMethodSupported();
    }
    
    forceZip64() {
        this.forceZip64_ = true;
    }
    
    isZip64Forced() {
        return this.forceZip64_;
    }
    
    get localHeaderRequiresZip64() {
        let result = this.forceZip64_;
        if (!result) {
            let trueCompressedSize = this.m_CompressedSize;
            if ((Utils.compareUnsigned(this.versionToExtract, 0) == 0) && this.isCrypted) 
                trueCompressedSize += ZipConstants.CRYPTO_HEADER_SIZE;
            result = ((Utils.compareUnsigned(this.versionToExtract, 0) == 0) || ((this.versionToExtract) >= ZipConstants.VERSION_ZIP64));
        }
        return result;
    }
    
    get centralHeaderRequiresZip64() {
        return this.localHeaderRequiresZip64;
    }
    
    get dosTime() {
        if (((this.m_Known.value()) & (ZipEntryKnown.TIME.value())) === (0)) 
            return 0;
        else 
            return this.m_DosTime;
    }
    set dosTime(value) {
        this.m_DosTime = value;
        this.m_Known = ZipEntryKnown.of((this.m_Known.value()) | (ZipEntryKnown.TIME.value()));
        return value;
    }
    
    get dateTime() {
        let sec = Math.min(59, 2 * (((this.m_DosTime) & 0x1f)));
        let min = Math.min(59, (((this.m_DosTime) >>> 5)) & 0x3f);
        let hrs = Math.min(23, (((this.m_DosTime) >>> 11)) & 0x1f);
        let mon = Math.max(1, Math.min(12, ((((this.m_DosTime) >>> 21)) & 0xf)));
        let year = (((((this.m_DosTime) >>> 25)) & 0x7f)) + 1980;
        let day = Math.max(1, Math.min(Utils.daysInMonth(year, mon), ((((this.m_DosTime) >>> 16)) & 0x1f)));
        return new Date(year, mon - 1, day, hrs, min, sec);
    }
    set dateTime(value) {
        let year = value.getFullYear();
        let month = Utils.getMonth(value);
        let day = value.getDate();
        let hour = value.getHours();
        let minute = value.getMinutes();
        let second = value.getSeconds();
        if (Utils.compareUnsigned(year, 1980) < 0) {
            year = 1980;
            month = 1;
            day = 1;
            hour = 0;
            minute = 0;
            second = 0;
        }
        else if (Utils.compareUnsigned(year, 2107) > 0) {
            year = 2107;
            month = 12;
            day = 31;
            hour = 23;
            minute = 59;
            second = 59;
        }
        this.dosTime = (((((((year) - 1980)) & 0x7f)) << 25 | ((month) << 21) | ((day) << 16)) | ((hour) << 11) | ((minute) << 5)) | (((second) >>> 1));
        return value;
    }
    
    get name() {
        return this.m_Name;
    }
    
    get size() {
        return (((this.m_Known.value()) & (ZipEntryKnown.SIZE.value())) !== (0) ? this.m_Size : -1);
    }
    set size(value) {
        this.m_Size = value;
        this.m_Known = ZipEntryKnown.of((this.m_Known.value()) | (ZipEntryKnown.SIZE.value()));
        return value;
    }
    
    get compressedSize() {
        return (((this.m_Known.value()) & (ZipEntryKnown.COMPRESSEDSIZE.value())) !== (0) ? this.m_CompressedSize : -1);
    }
    set compressedSize(value) {
        this.m_CompressedSize = value;
        this.m_Known = ZipEntryKnown.of((this.m_Known.value()) | (ZipEntryKnown.COMPRESSEDSIZE.value()));
        return value;
    }
    
    get crc() {
        return (((this.m_Known.value()) & (ZipEntryKnown.CRC.value())) !== (0) ? this.m_Crc : 0);
    }
    set crc(value) {
        this.m_Crc = value;
        this.m_Known = ZipEntryKnown.of((this.m_Known.value()) | (ZipEntryKnown.CRC.value()));
        return value;
    }
    
    get crcOk() {
        return ((this.m_Known.value()) & (ZipEntryKnown.CRC.value())) !== (0);
    }
    
    get _CompressionMethod() {
        return this.m_Method;
    }
    set _CompressionMethod(value) {
        if (!ZipEntry.isCompressionMethodSupportedEx(value)) 
            throw new Error("Compression method not supported");
        this.m_Method = value;
        return value;
    }
    
    get compressionMethodForHeader() {
        return (this.aESKeySize > 0 ? CompressionMethod.WINZIPAES : this.m_Method);
    }
    
    get extraData() {
        return this.m_Extra;
    }
    set extraData(value) {
        if (value === null) 
            this.m_Extra = null;
        else {
            if (value.length > 0xffff) 
                throw new Error("value");
            this.m_Extra = new Uint8Array(value.length);
            for(let iii = 0; iii < (value.length); iii++) this.m_Extra[(0) + iii] = value[(0) + iii];
        }
        return value;
    }
    
    get aESKeySize() {
        switch (this._aesEncryptionStrength) { 
        case 0: {
            return 0;
        }
        case 1: {
            return 128;
        }
        case 2: {
            return 192;
        }
        case 3: {
            return 256;
        }
        default: {
            throw new Error("Invalid AESEncryptionStrength " + (String(this._aesEncryptionStrength)));
        }
        }
    }
    set aESKeySize(value) {
        switch (value) { 
        case 0: {
            this._aesEncryptionStrength = 0;
            break;
        }
        case 128: {
            this._aesEncryptionStrength = 1;
            break;
        }
        case 256: {
            this._aesEncryptionStrength = 3;
            break;
        }
        default: {
            throw new Error("AESKeySize must be 0, 128 or 256: " + (String(value)));
        }
        }
        return value;
    }
    
    get aESEncryptionStrength() {
        return this._aesEncryptionStrength;
    }
    
    get aESSaltLen() {
        return Utils.intDiv(this.aESKeySize, 16);
    }
    
    get aESOverheadSize() {
        return 12 + this.aESSaltLen;
    }
    
    processExtraData(localHeader) {
        let _extraData = new ZipExtraData(this.m_Extra);
        if (_extraData.find(0x0001)) {
            this.forceZip64_ = true;
            if (_extraData.valueLength < 4) 
                return;
            if (localHeader || (this.m_Size === 2147483647)) 
                this.m_Size = _extraData.readLong();
            if (localHeader || (this.m_CompressedSize === 2147483647)) 
                this.m_CompressedSize = _extraData.readLong();
            if (!localHeader && (this.m_Ooffset === 2147483647)) 
                this.m_Ooffset = _extraData.readLong();
        }
        else if (((((this.versionToExtract) & 0xff)) >= ZipConstants.VERSION_ZIP64) && (((this.m_Size === 2147483647) || (this.m_CompressedSize === 2147483647)))) 
            throw new Error("Zip64 Extended information required but is missing.");
        if (_extraData.find(10)) {
            if (_extraData.valueLength < 4) 
                throw new Error("NTFS Extra data invalid");
            _extraData.readInt();
            while (_extraData.unreadCount >= 4) {
                let ntfsTag = _extraData.readShort();
                let ntfsLength = _extraData.readShort();
                if (ntfsTag === 1) {
                    if (ntfsLength >= 24) {
                        let lastModification = _extraData.readLong();
                        let lastAccess = _extraData.readLong();
                        let createTime = _extraData.readLong();
                    }
                    break;
                }
                else 
                    _extraData.skip(ntfsLength);
            }
        }
        else if (_extraData.find(0x5455)) {
            let length = _extraData.valueLength;
            let _flags = _extraData.readByte();
            if ((((_flags & 1)) !== 0) && (length >= 5)) {
                let iTime = _extraData.readInt();
                this.dateTime = Utils.addSeconds(new Date(1970, 1 - 1, 1, 0, 0, 0), iTime);
            }
        }
        if (this.m_Method === CompressionMethod.WINZIPAES) 
            this.processAESExtraData(_extraData);
    }
    
    processAESExtraData(_extraData) {
        if (_extraData.find(0x9901)) {
            this.versionToExtract = ZipConstants.VERSION_AES;
            this.flags = this.flags | (GeneralBitFlags.STRONGENCRYPTION.value());
            let length = _extraData.valueLength;
            if (length < 7) 
                throw new Error("AES Extra Data Length " + (String(length)) + " invalid.");
            let ver = _extraData.readShort();
            let vendorId = _extraData.readShort();
            let encrStrength = _extraData.readByte();
            let actualCompress = _extraData.readShort();
            this._aesVer = ver;
            this._aesEncryptionStrength = encrStrength;
            this.m_Method = CompressionMethod.of(actualCompress);
        }
        else 
            throw new Error("AES Extra Data missing");
    }
    
    get comment() {
        return this.m_Comment;
    }
    set comment(value) {
        if ((value !== null) && (value.length > 0xffff)) 
            throw new Error("value", "cannot exceed 65535");
        this.m_Comment = value;
        return value;
    }
    
    get isDirectory() {
        let nameLength = this.m_Name.length;
        let result = (((nameLength > 0) && (((this.m_Name[nameLength - 1] === '/') || (this.m_Name[nameLength - 1] === '\\'))))) || this.hasDosAttributes(16);
        return result;
    }
    
    get isFile() {
        return !this.isDirectory && !this.hasDosAttributes(8);
    }
    
    isCompressionMethodSupported() {
        return ZipEntry.isCompressionMethodSupportedEx(this._CompressionMethod);
    }
    
    clone() {
        return this;
    }
    
    toString() {
        return (this.m_Name + ": " + this.m_CompressedSize);
    }
    
    static isCompressionMethodSupportedEx(method) {
        return (method === CompressionMethod.DEFLATED) || (method === CompressionMethod.STORED);
    }
}


module.exports = ZipEntry