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 ZlibDecompressor
        implements Compression {

    private static final int BUFFER_SIZE = 1024 * 36;

    private final ZStream stream;
    private final byte[] buffer;
    private int decompressedDataLength;
    private boolean decompressionCompleted;

    public ZlibDecompressor() {
        this.stream = new ZStream();
        this.stream.inflateInit();

        this.buffer = new byte[BUFFER_SIZE];
    }

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

            do {
                inflate();

                if (!this.decompressionCompleted) {
                    out.write(this.buffer, 0, this.decompressedDataLength);
                    totalDecompressedDataLength += this.decompressedDataLength;
                }
            } while (!this.decompressionCompleted);

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

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

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

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

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

        this.decompressedDataLength = this.buffer.length - stream.availOut;
        this.decompressionCompleted = status == JZlib.Z_BUF_ERROR;
    }

}
