// 
// Decompiled by Procyon v0.6.0
// 

package io.netty.handler.codec.compression;

import java.io.IOException;
import java.io.Closeable;
import java.io.InputStream;
import java.util.List;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.internal.ObjectUtil;
import com.github.luben.zstd.ZstdInputStreamNoFinalizer;
import io.netty.handler.codec.ByteToMessageDecoder;

public final class ZstdDecoder extends ByteToMessageDecoder
{
    private final int maximumAllocationSize;
    private final MutableByteBufInputStream inputStream;
    private ZstdInputStreamNoFinalizer zstdIs;
    private boolean needsRead;
    private State currentState;
    
    public ZstdDecoder() {
        this(4194304);
    }
    
    public ZstdDecoder(final int maximumAllocationSize) {
        try {
            Zstd.ensureAvailability();
        }
        catch (final Throwable throwable) {
            throw new ExceptionInInitializerError(throwable);
        }
        this.inputStream = new MutableByteBufInputStream();
        this.currentState = State.DECOMPRESS_DATA;
        this.maximumAllocationSize = ObjectUtil.checkPositiveOrZero(maximumAllocationSize, "maximumAllocationSize");
    }
    
    @Override
    protected void decode(final ChannelHandlerContext ctx, final ByteBuf in, final List<Object> out) throws Exception {
        this.needsRead = true;
        try {
            if (this.currentState == State.CORRUPTED) {
                in.skipBytes(in.readableBytes());
                return;
            }
            this.inputStream.current = in;
            ByteBuf outBuffer = null;
            final int compressedLength = in.readableBytes();
            try {
                long uncompressedLength;
                if (in.isDirect()) {
                    uncompressedLength = com.github.luben.zstd.Zstd.getFrameContentSize(CompressionUtil.safeNioBuffer(in, in.readerIndex(), in.readableBytes()));
                }
                else {
                    uncompressedLength = com.github.luben.zstd.Zstd.getFrameContentSize(in.array(), in.readerIndex() + in.arrayOffset(), in.readableBytes());
                }
                if (uncompressedLength <= 0L) {
                    uncompressedLength = compressedLength * 2L;
                }
                int w;
                do {
                    if (outBuffer == null) {
                        outBuffer = ctx.alloc().heapBuffer((int)((this.maximumAllocationSize == 0) ? uncompressedLength : Math.min(this.maximumAllocationSize, uncompressedLength)));
                    }
                    do {
                        w = outBuffer.writeBytes(this.zstdIs, outBuffer.writableBytes());
                    } while (w != -1 && outBuffer.isWritable());
                    if (outBuffer.isReadable()) {
                        this.needsRead = false;
                        ctx.fireChannelRead((Object)outBuffer);
                        outBuffer = null;
                    }
                } while (w != -1);
            }
            finally {
                if (outBuffer != null) {
                    outBuffer.release();
                }
            }
        }
        catch (final Exception e) {
            this.currentState = State.CORRUPTED;
            throw new DecompressionException(e);
        }
        finally {
            this.inputStream.current = null;
        }
    }
    
    @Override
    public void channelReadComplete(final ChannelHandlerContext ctx) throws Exception {
        this.discardSomeReadBytes();
        if (this.needsRead && !ctx.channel().config().isAutoRead()) {
            ctx.read();
        }
        ctx.fireChannelReadComplete();
    }
    
    @Override
    public void handlerAdded(final ChannelHandlerContext ctx) throws Exception {
        super.handlerAdded(ctx);
        (this.zstdIs = new ZstdInputStreamNoFinalizer(this.inputStream)).setContinuous(true);
    }
    
    @Override
    protected void handlerRemoved0(final ChannelHandlerContext ctx) throws Exception {
        try {
            closeSilently(this.zstdIs);
        }
        finally {
            super.handlerRemoved0(ctx);
        }
    }
    
    private static void closeSilently(final Closeable closeable) {
        if (closeable != null) {
            try {
                closeable.close();
            }
            catch (final IOException ex) {}
        }
    }
    
    private enum State
    {
        DECOMPRESS_DATA, 
        CORRUPTED;
    }
    
    private static final class MutableByteBufInputStream extends InputStream
    {
        ByteBuf current;
        
        @Override
        public int read() {
            if (this.current == null || !this.current.isReadable()) {
                return -1;
            }
            return this.current.readByte() & 0xFF;
        }
        
        @Override
        public int read(final byte[] b, final int off, int len) {
            final int available = this.available();
            if (available == 0) {
                return -1;
            }
            len = Math.min(available, len);
            this.current.readBytes(b, off, len);
            return len;
        }
        
        @Override
        public int available() {
            return (this.current == null) ? 0 : this.current.readableBytes();
        }
    }
}
