package org.zywx.wbpalmstar.plugin.uexzip.de.idyl.winzipaes.impl;

import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;

/**
 * Provide InputStream access to <b>compressed data</b> from one ZipEntry contained
 * within one ZipFile. Necessary as java.util.zip.ZipInputStream only provides access to
 * the <b>uncompressed data</b>.
 *
 * @author olaf@merkert.de
 */
public class ZipFileEntryInputStream implements ZipConstants {

    private static final Logger LOG = Logger.getLogger(ZipFileEntryInputStream.class.getName());

    protected FileInputStream fis;

    protected long startPos;

    protected long endPos;

    protected long currentPos;

    protected long compressedSize;

    public long getCompressedSize() {
        return this.compressedSize;
    }

    public ZipFileEntryInputStream(String fileName) throws IOException {
        fis = new FileInputStream(fileName);
    }

    /**
     * position input stream to start of ZipEntry this instance was created for
     *
     * @throws IOException
     */
    public void nextEntry(ZipEntry ze) throws IOException {
        LOG.fine("nextEntry().currentPos=" + currentPos);

        byte[] intBuffer = new byte[4];
        int bytesRead = fis.read(intBuffer);
        LOG.fine("bytes read=" + bytesRead);
        if (bytesRead == -1) {
            // this occurred on android once, with FileInputStream as my superclass
            throw new IOException("no data available - available=" + fis.available());
        }

        int dataDescriptorLength = 0;
        if (Arrays.equals(intBuffer, new byte[]{0x50, 0x4b, 0x07, 0x08})) {
            // header does not belong to next file, but is start of the "data descriptor" of last file
            // skip this data descriptor containing crc32(4), compressedSize(4), uncompressedSize(4)
            dataDescriptorLength = 4 + 4 + 4;
            fis.skip(dataDescriptorLength);
            // read local file header signature
            fis.read(intBuffer);
        }

        if (!Arrays.equals(intBuffer, new byte[]{0x50, 0x4b, 0x03, 0x04})) {
            throw new IOException("wrong local file header signature - value=" + ByteArrayHelper.toString(intBuffer));
        }

        // info only - if bit-3 is set, current entry is followed by data descriptor
        boolean hasDataDescriptor = (ze.getMethod() & 8) > 0;
        LOG.fine("nextEntry().hasDataDescriptor=" + hasDataDescriptor);

        this.compressedSize = ze.getCompressedSize();

        fis.skip(14 + 4 + 4); // 14 + localFileHeaderSignature(4) + compressedSize(4) + size(4)

        byte[] shortBuffer = new byte[2];
        fis.read(shortBuffer);
        int fileNameLength = ByteArrayHelper.toInt(shortBuffer);

        fis.read(shortBuffer);
        int extraFieldLength = ByteArrayHelper.toInt(shortBuffer);

        startPos = 18 + 12 + fileNameLength + extraFieldLength + dataDescriptorLength;
        currentPos = startPos;
        endPos = startPos + this.compressedSize;

        fis.skip(fileNameLength + extraFieldLength);
    }

    // should work without this, but never trust an OO system
    public int read(byte[] b) throws IOException {
        return this.read(b, 0, b.length);
    }

    public int read(byte[] b, int off, int len) throws IOException {
        int bytesRead = -1;
        int remainingBytes = (int) (endPos - currentPos);
        if (remainingBytes > 0) {
            if (currentPos + len < endPos) {
                bytesRead = fis.read(b, off, len);
                currentPos += bytesRead;
            } else {
                bytesRead = fis.read(b, off, remainingBytes);
                currentPos += bytesRead;
            }
        }
        return bytesRead;
    }

    public void close() throws IOException {
        fis.close();
    }

}
