package com.jscape.inet.ssh.protocol.v2.marshaling.algorithms;

import com.jscape.inet.ssh.protocol.v2.marshaling.algorithms.zlib.JZlib;
import com.jscape.inet.ssh.protocol.v2.marshaling.algorithms.zlib.ZStream;

import java.io.OutputStream;

/**
 * @author Alex Usun
 */
public class ZlibCompressor
        implements Compression {

    private static final int LEVEL = 6;
    private static final int BUFFER_SIZE = 1024 * 36;

    private final ZStream stream;
    private final byte[] buffer;
    private int compressedDataLength;
    private boolean compressionCompleted;

    public ZlibCompressor() {
        this.stream = new ZStream();
        this.stream.deflateInit(LEVEL);

        this.buffer = new byte[BUFFER_SIZE];
    }

    @Override
    public int apply(byte[] data, int offset, int length, OutputStream out)
            throws OperationException {
        try {
            initBlock(data, offset, length);
            int totalCompressedDataLength = 0;

            do {
                deflate();
                out.write(this.buffer, 0, this.compressedDataLength);
                totalCompressedDataLength += this.compressedDataLength;
            } while (!this.compressionCompleted);

            return totalCompressedDataLength;
        } catch (Exception e) {
            throw new OperationException(e);
        }
    }

    private void initBlock(byte[] data, int offset, int length) {
        this.stream.nextIn = data;
        this.stream.nextInIndex = offset;
        this.stream.availIn = length;
        this.compressionCompleted = false;
    }

    private void deflate()
            throws Exception {
        this.stream.nextOut = this.buffer;
        this.stream.nextOutIndex = 0;
        this.stream.availOut = this.buffer.length;

        int status = this.stream.deflate(JZlib.Z_PARTIAL_FLUSH);

        if (status != JZlib.Z_OK) {
            throw new Exception("Zlib compression error. Status: " + status);
        }

        this.compressedDataLength = this.buffer.length - stream.availOut;
        this.compressionCompleted = this.stream.availOut != 0;
    }

}
