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

package io.netty.handler.codec.compression;

import java.nio.channels.ClosedChannelException;
import java.nio.ByteBuffer;
import io.netty.util.ReferenceCountUtil;
import com.aayushatharva.brotli4j.encoder.BrotliEncoderChannel;
import java.nio.channels.WritableByteChannel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelPromise;
import java.io.IOException;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.internal.ObjectUtil;
import com.aayushatharva.brotli4j.encoder.Encoder;
import io.netty.util.AttributeKey;
import io.netty.channel.ChannelHandler;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.MessageToByteEncoder;

@ChannelHandler.Sharable
public final class BrotliEncoder extends MessageToByteEncoder<ByteBuf>
{
    private static final AttributeKey<Writer> ATTR;
    private final Encoder.Parameters parameters;
    private final boolean isSharable;
    private Writer writer;
    
    public BrotliEncoder() {
        this(BrotliOptions.DEFAULT);
    }
    
    public BrotliEncoder(final BrotliOptions brotliOptions) {
        this(brotliOptions.parameters());
    }
    
    public BrotliEncoder(final Encoder.Parameters parameters) {
        this(parameters, true);
    }
    
    public BrotliEncoder(final Encoder.Parameters parameters, final boolean isSharable) {
        super(ByteBuf.class);
        this.parameters = ObjectUtil.checkNotNull(parameters, "Parameters");
        this.isSharable = isSharable;
    }
    
    @Override
    public void handlerAdded(final ChannelHandlerContext ctx) throws Exception {
        final Writer writer = new Writer(this.parameters, ctx);
        if (this.isSharable) {
            ctx.channel().attr(BrotliEncoder.ATTR).set(writer);
        }
        else {
            this.writer = writer;
        }
        super.handlerAdded(ctx);
    }
    
    @Override
    public void handlerRemoved(final ChannelHandlerContext ctx) throws Exception {
        this.finish(ctx);
        super.handlerRemoved(ctx);
    }
    
    @Override
    protected void encode(final ChannelHandlerContext ctx, final ByteBuf msg, final ByteBuf out) throws Exception {
    }
    
    @Override
    protected ByteBuf allocateBuffer(final ChannelHandlerContext ctx, final ByteBuf msg, final boolean preferDirect) throws Exception {
        if (!msg.isReadable()) {
            return Unpooled.EMPTY_BUFFER;
        }
        Writer writer;
        if (this.isSharable) {
            writer = ctx.channel().attr(BrotliEncoder.ATTR).get();
        }
        else {
            writer = this.writer;
        }
        if (writer == null) {
            return Unpooled.EMPTY_BUFFER;
        }
        writer.encode(msg, preferDirect);
        return writer.writableBuffer;
    }
    
    @Override
    public boolean isSharable() {
        return this.isSharable;
    }
    
    public void finish(final ChannelHandlerContext ctx) throws IOException {
        this.finishEncode(ctx, ctx.newPromise());
    }
    
    private ChannelFuture finishEncode(final ChannelHandlerContext ctx, final ChannelPromise promise) throws IOException {
        Writer writer;
        if (this.isSharable) {
            writer = ctx.channel().attr(BrotliEncoder.ATTR).getAndSet(null);
        }
        else {
            writer = this.writer;
        }
        if (writer != null) {
            writer.close();
            this.writer = null;
        }
        return promise;
    }
    
    @Override
    public void close(final ChannelHandlerContext ctx, final ChannelPromise promise) throws Exception {
        final ChannelFuture f = this.finishEncode(ctx, ctx.newPromise());
        EncoderUtil.closeAfterFinishEncode(ctx, f, promise);
    }
    
    static {
        ATTR = AttributeKey.valueOf("BrotliEncoderWriter");
    }
    
    private static final class Writer implements WritableByteChannel
    {
        private ByteBuf writableBuffer;
        private final BrotliEncoderChannel brotliEncoderChannel;
        private final ChannelHandlerContext ctx;
        private boolean isClosed;
        
        private Writer(final Encoder.Parameters parameters, final ChannelHandlerContext ctx) throws IOException {
            this.brotliEncoderChannel = new BrotliEncoderChannel((WritableByteChannel)this, parameters);
            this.ctx = ctx;
        }
        
        private void encode(final ByteBuf msg, final boolean preferDirect) throws Exception {
            try {
                this.allocate(preferDirect);
                final ByteBuffer nioBuffer = CompressionUtil.safeReadableNioBuffer(msg);
                final int position = nioBuffer.position();
                this.brotliEncoderChannel.write(nioBuffer);
                msg.skipBytes(nioBuffer.position() - position);
                this.brotliEncoderChannel.flush();
            }
            catch (final Exception e) {
                ReferenceCountUtil.release(msg);
                throw e;
            }
        }
        
        private void allocate(final boolean preferDirect) {
            if (preferDirect) {
                this.writableBuffer = this.ctx.alloc().ioBuffer();
            }
            else {
                this.writableBuffer = this.ctx.alloc().buffer();
            }
        }
        
        @Override
        public int write(final ByteBuffer src) throws IOException {
            if (!this.isOpen()) {
                throw new ClosedChannelException();
            }
            return this.writableBuffer.writeBytes(src).readableBytes();
        }
        
        @Override
        public boolean isOpen() {
            return !this.isClosed;
        }
        
        @Override
        public void close() {
            final ChannelPromise promise = this.ctx.newPromise();
            this.ctx.executor().execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Writer.this.finish(promise);
                    }
                    catch (final IOException ex) {
                        promise.setFailure((Throwable)new IllegalStateException("Failed to finish encoding", ex));
                    }
                }
            });
        }
        
        public void finish(final ChannelPromise promise) throws IOException {
            if (!this.isClosed) {
                this.allocate(true);
                try {
                    this.brotliEncoderChannel.close();
                    this.isClosed = true;
                }
                catch (final Exception ex) {
                    promise.setFailure((Throwable)ex);
                    ReferenceCountUtil.release(this.writableBuffer);
                    return;
                }
                this.ctx.writeAndFlush(this.writableBuffer, promise);
            }
        }
    }
}
