/*
 * 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
 */

package com.pullenti.unitext.internal.zip;

// This is a DeflaterOutputStream that writes the files into a zip
// archive one after another.  It has a special method to start a new
// zip entry.  The zip entries contains information about the file name
// size, compressed size, CRC, etc.
// It includes support for Stored and Deflated entries.
// This class is not thread safe.
public class ZipOutputStream extends DeflaterOutputStream {

    public ZipOutputStream(com.pullenti.unisharp.Stream baseOutputStream, int bufferSize) {
        super(baseOutputStream, new Deflater(Deflater.DEFAULT_COMPRESSION, true), bufferSize);
    }

    public boolean isFinished() {
        return entries == null;
    }


    public void setComment(String comment) throws IndexOutOfBoundsException {
        byte[] commentBytes = ZipConstants.convertToArrayStr(comment);
        if (commentBytes.length > 0xffff) 
            throw new IndexOutOfBoundsException("comment");
        zipComment = commentBytes;
    }

    public void setLevel(int level) throws IndexOutOfBoundsException, Exception {
        deflater_.setLevel(level);
        defaultCompressionLevel = level;
    }

    public int getLevel() {
        return deflater_.getLevel();
    }

    public UseZip64 getUseZip64() {
        return useZip64_;
    }

    public UseZip64 setUseZip64(UseZip64 value) {
        useZip64_ = value;
        return value;
    }


    private void writeLeShort(int value) throws java.io.IOException {
        /* unchecked C# block !!! */ {
            baseOutputStream_.write((byte)((value & 0xff)));
            baseOutputStream_.write((byte)((((value >> 8)) & 0xff)));
        }
    }

    private void writeLeInt(int value) throws java.io.IOException {
        /* unchecked C# block !!! */ {
            this.writeLeShort(value);
            this.writeLeShort(value >> 16);
        }
    }

    private void writeLeLong(long value) throws java.io.IOException {
        /* unchecked C# block !!! */ {
            this.writeLeInt((int)value);
            this.writeLeInt((int)((value >> 32)));
        }
    }

    public void putNextEntry(ZipEntry entry) throws IllegalArgumentException, UnsupportedOperationException, Exception, java.io.IOException, UnsupportedOperationException, IndexOutOfBoundsException, java.io.FileNotFoundException {
        if (entry == null) 
            throw new IllegalArgumentException("entry");
        if (entries == null) 
            throw new UnsupportedOperationException("ZipOutputStream was finished");
        if (curEntry != null) 
            this.closeEntry();
        if (entries.size() == Integer.MAX_VALUE) 
            throw new Exception("Too many entries for Zip file");
        CompressionMethod method = entry.get_CompressionMethod();
        int compressionLevel = defaultCompressionLevel;
        entry.flags &= (GeneralBitFlags.UNICODETEXT.value());
        patchEntryHeader = false;
        boolean headerInfoAvailable;
        if (entry.getSize() == 0) {
            entry.setCompressedSize(entry.getSize());
            entry.setCrc((int)0);
            method = CompressionMethod.STORED;
            headerInfoAvailable = true;
        }
        else {
            headerInfoAvailable = (entry.getSize() >= 0) && entry.getHasCrc();
            if (method == CompressionMethod.STORED) {
                if (!headerInfoAvailable) {
                    if (!canPatchEntries()) {
                        method = CompressionMethod.DEFLATED;
                        compressionLevel = 0;
                    }
                }
                else {
                    entry.setCompressedSize(entry.getSize());
                    headerInfoAvailable = entry.getHasCrc();
                }
            }
        }
        if (headerInfoAvailable == false) {
            if (canPatchEntries() == false) 
                entry.flags |= 8;
            else 
                patchEntryHeader = true;
        }
        entry.setOffset(offset);
        entry.set_CompressionMethod(method);
        curMethod = method;
        sizePatchPos = -1L;
        if ((useZip64_ == UseZip64.ON) || ((((entry.getSize() < 0)) && (useZip64_ == UseZip64.DYNAMIC)))) 
            entry.forceZip64();
        this.writeLeInt(ZipConstants.LOCALHEADERSIGNATURE);
        this.writeLeShort(entry.getVersion());
        this.writeLeShort(entry.flags);
        this.writeLeShort(Byte.toUnsignedInt((byte)entry.getCompressionMethodForHeader().value()));
        this.writeLeInt((int)entry.getDosTime());
        if (headerInfoAvailable == true) {
            this.writeLeInt((int)entry.getCrc());
            if (entry.getLocalHeaderRequiresZip64()) {
                this.writeLeInt(-1);
                this.writeLeInt(-1);
            }
            else {
                this.writeLeInt((entry.isCrypted() ? (entry.getCompressedSize()) + ZipConstants.CRYPTOHEADERSIZE : entry.getCompressedSize()));
                this.writeLeInt(entry.getSize());
            }
        }
        else {
            if (patchEntryHeader) 
                crcPatchPos = baseOutputStream_.getPosition();
            this.writeLeInt(0);
            if (patchEntryHeader) 
                sizePatchPos = baseOutputStream_.getPosition();
            if (entry.getLocalHeaderRequiresZip64() || patchEntryHeader) {
                this.writeLeInt(-1);
                this.writeLeInt(-1);
            }
            else {
                this.writeLeInt(0);
                this.writeLeInt(0);
            }
        }
        byte[] name = ZipConstants.convertToArray(entry.flags, entry.getName());
        if (name.length > 0xFFFF) 
            throw new Exception("Entry name too long.");
        ZipExtraData ed = new ZipExtraData(entry.getExtraData());
        if (entry.getLocalHeaderRequiresZip64()) {
            ed.startNewEntry();
            if (headerInfoAvailable) {
                ed.addLeLong(entry.getSize(), 0);
                ed.addLeLong(entry.getCompressedSize(), 0);
            }
            else {
                ed.addLeLong(-1, -1);
                ed.addLeLong(-1, -1);
            }
            ed.addNewEntry(1);
            if (!ed.find(1)) 
                throw new Exception("Internal error cant find extra data");
            if (patchEntryHeader) 
                sizePatchPos = (long)ed.getCurrentReadIndex();
        }
        else 
            ed.delete(1);
        byte[] extra = ed.getEntryData();
        this.writeLeShort(name.length);
        this.writeLeShort(extra.length);
        if (name.length > 0) 
            baseOutputStream_.write(name, 0, name.length);
        if (entry.getLocalHeaderRequiresZip64() && patchEntryHeader) 
            sizePatchPos += baseOutputStream_.getPosition();
        if (extra.length > 0) 
            baseOutputStream_.write(extra, 0, extra.length);
        offset += (ZipConstants.LOCALHEADERBASESIZE + name.length + extra.length);
        if (entry.getAESKeySize() > 0) 
            offset += entry.getAESOverheadSize();
        curEntry = entry;
        crc.reset();
        if (method == CompressionMethod.DEFLATED) {
            deflater_.reset();
            deflater_.setLevel(compressionLevel);
        }
        size = 0;
    }

    public void closeEntry() throws UnsupportedOperationException, Exception, java.io.IOException, java.io.FileNotFoundException, IndexOutOfBoundsException {
        if (curEntry == null) 
            throw new UnsupportedOperationException("No open entry");
        int csize = size;
        if (curMethod == CompressionMethod.DEFLATED) {
            if (size >= 0) {
                super.finish();
                csize = deflater_.getTotalOut();
            }
            else 
                deflater_.reset();
        }
        if (curEntry.getSize() < 0) 
            curEntry.setSize(size);
        else if (curEntry.getSize() != size) 
            throw new Exception(("size was " + (((Integer)size).toString()) + ", but I expected ") + (((Integer)curEntry.getSize()).toString()));
        if (curEntry.getCompressedSize() < 0) 
            curEntry.setCompressedSize(csize);
        else if (curEntry.getCompressedSize() != csize) 
            throw new Exception(("compressed size was " + (((Integer)csize).toString()) + ", but I expected ") + (((Integer)curEntry.getCompressedSize()).toString()));
        if (!curEntry.getCrcOk()) 
            curEntry.setCrc(crc.getValue());
        else if (Integer.compareUnsigned(curEntry.getCrc(), crc.getValue()) != 0) 
            throw new Exception(("crc was " + (((Integer)crc.getValue()).toString()) + ", but I expected ") + (((Integer)curEntry.getCrc()).toString()));
        offset += csize;
        if (curEntry.isCrypted()) {
            if (curEntry.getAESKeySize() > 0) 
                curEntry.setCompressedSize(curEntry.getCompressedSize() + curEntry.getAESOverheadSize());
            else 
                curEntry.setCompressedSize(curEntry.getCompressedSize() + ZipConstants.CRYPTOHEADERSIZE);
        }
        if (patchEntryHeader) {
            patchEntryHeader = false;
            long curPos = baseOutputStream_.getPosition();
            baseOutputStream_.seek(crcPatchPos, 0);
            this.writeLeInt((int)curEntry.getCrc());
            if (curEntry.getLocalHeaderRequiresZip64()) {
                if (sizePatchPos == (-1L)) 
                    throw new Exception("Entry requires zip64 but this has been turned off");
                baseOutputStream_.seek(sizePatchPos, 0);
                this.writeLeLong((long)curEntry.getSize());
                this.writeLeLong((long)curEntry.getCompressedSize());
            }
            else {
                this.writeLeInt(curEntry.getCompressedSize());
                this.writeLeInt(curEntry.getSize());
            }
            baseOutputStream_.seek(curPos, 0);
        }
        if (((curEntry.flags & 8)) != 0) {
            this.writeLeInt(ZipConstants.DATADESCRIPTORSIGNATURE);
            this.writeLeInt((int)curEntry.getCrc());
            if (curEntry.getLocalHeaderRequiresZip64()) {
                this.writeLeLong((long)curEntry.getCompressedSize());
                this.writeLeLong((long)curEntry.getSize());
                offset += ZipConstants.ZIP64DATADESCRIPTORSIZE;
            }
            else {
                this.writeLeInt(curEntry.getCompressedSize());
                this.writeLeInt(curEntry.getSize());
                offset += ZipConstants.DATADESCRIPTORSIZE;
            }
        }
        entries.add(curEntry);
        curEntry = null;
    }

    @Override
    public void write(byte[] buffer, int _offset, int count) {
        try {
            if (curEntry == null) 
                throw new java.io.IOException("No open entry.");
            if (buffer == null) 
                throw new java.io.IOException("null buffer");
            if (_offset < 0) 
                throw new Exception("offset out of range");
            if (count < 0) 
                throw new Exception("bad count");
            if (((buffer.length - _offset)) < count) 
                throw new java.io.IOException("Invalid offset/count combination");
            try {
                crc.updateByBufEx(buffer, _offset, count);
            } catch (Exception ex) {
                throw new java.io.IOException(ex.getMessage(), ex);
            }
            size += count;
            try {
                switch ((curMethod).value()) { 
                case 8: // CompressionMethod.DEFLATED 
                    super.write(buffer, _offset, count);
                    break;
                case 0: // CompressionMethod.STORED 
                    baseOutputStream_.write(buffer, _offset, count);
                    break;
            }
            } catch (Exception ex) {
                throw new java.io.IOException(ex.getMessage(), ex);
            }
        } catch (Exception ex123) {
        }
    }

    @Override
    public void finish() throws Exception, java.io.IOException, java.io.FileNotFoundException, IndexOutOfBoundsException, UnsupportedOperationException {
        if (entries == null) 
            return;
        if (curEntry != null) 
            this.closeEntry();
        int numEntries = entries.size();
        int sizeEntries = 0;
        for (Object en : entries) {
            ZipEntry entry = (ZipEntry)com.pullenti.unisharp.Utils.cast(en, ZipEntry.class);
            if (entry == null) 
                continue;
            this.writeLeInt(ZipConstants.CENTRALHEADERSIGNATURE);
            this.writeLeShort(ZipConstants.VERSIONMADEBY);
            this.writeLeShort(entry.getVersion());
            this.writeLeShort(entry.flags);
            this.writeLeShort((int)((short)entry.getCompressionMethodForHeader().value()));
            this.writeLeInt((int)entry.getDosTime());
            this.writeLeInt((int)entry.getCrc());
            if (entry.isZip64Forced() || (Long.compareUnsigned(Integer.toUnsignedLong(entry.getCompressedSize()), 4294967295L) >= 0)) 
                this.writeLeInt(-1);
            else 
                this.writeLeInt(entry.getCompressedSize());
            if (entry.isZip64Forced() || (Long.compareUnsigned(Integer.toUnsignedLong(entry.getSize()), 4294967295L) >= 0)) 
                this.writeLeInt(-1);
            else 
                this.writeLeInt(entry.getSize());
            byte[] name = ZipConstants.convertToArray(entry.flags, entry.getName());
            if (name.length > 0xffff) 
                throw new Exception("Name too long.");
            ZipExtraData ed = new ZipExtraData(entry.getExtraData());
            if (entry.getCentralHeaderRequiresZip64()) {
                ed.startNewEntry();
                if (entry.isZip64Forced()) 
                    ed.addLeLong(entry.getSize(), 0);
                if (entry.isZip64Forced()) 
                    ed.addLeLong(entry.getCompressedSize(), 0);
                ed.addNewEntry(1);
            }
            else 
                ed.delete(1);
            byte[] extra = ed.getEntryData();
            byte[] entryComment = (entry.getComment() != null ? ZipConstants.convertToArray(entry.flags, entry.getComment()) : new byte[0]);
            if (entryComment.length > 0xffff) 
                throw new Exception("Comment too long.");
            this.writeLeShort(name.length);
            this.writeLeShort(extra.length);
            this.writeLeShort(entryComment.length);
            this.writeLeShort(0);
            this.writeLeShort(0);
            if (entry.getExternalFileAttributes() != -1) 
                this.writeLeInt(entry.getExternalFileAttributes());
            else if (entry.isDirectory()) 
                this.writeLeInt(16);
            else 
                this.writeLeInt(0);
            if (Long.compareUnsigned(Integer.toUnsignedLong(entry.getOffset()), 4294967295L) >= 0) 
                this.writeLeInt(-1);
            else 
                this.writeLeInt(entry.getOffset());
            if (name.length > 0) 
                baseOutputStream_.write(name, 0, name.length);
            if (extra.length > 0) 
                baseOutputStream_.write(extra, 0, extra.length);
            if (entryComment.length > 0) 
                baseOutputStream_.write(entryComment, 0, entryComment.length);
            sizeEntries += ((ZipConstants.CENTRALHEADERBASESIZE + name.length + extra.length) + entryComment.length);
        }
        try (ZipHelperStream zhs = new ZipHelperStream(null, baseOutputStream_)) {
            zhs.writeEndOfCentralDirectory(numEntries, sizeEntries, offset, zipComment);
        }
        entries = null;
    }

    private java.util.ArrayList<Object> entries = new java.util.ArrayList<Object>();

    private Crc32 crc = new Crc32();

    private ZipEntry curEntry;

    private int defaultCompressionLevel = Deflater.DEFAULT_COMPRESSION;

    private CompressionMethod curMethod = CompressionMethod.DEFLATED;

    private int size;

    private int offset;

    private byte[] zipComment = new byte[0];

    private boolean patchEntryHeader;

    private long crcPatchPos = -1L;

    private long sizePatchPos = -1L;

    private UseZip64 useZip64_ = UseZip64.DYNAMIC;
    public ZipOutputStream() {
        super();
    }
}
