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

package io.netty.handler.codec.http;

import io.netty.util.LeakPresenceDetector;
import io.netty.util.CharsetUtil;
import java.util.Iterator;
import java.util.Map;
import io.netty.buffer.ByteBufUtil;
import io.netty.channel.FileRegion;
import io.netty.util.ReferenceCountUtil;
import io.netty.buffer.Unpooled;
import io.netty.util.concurrent.Promise;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.PromiseCombiner;
import io.netty.handler.codec.EncoderException;
import io.netty.util.internal.StringUtil;
import io.netty.channel.ChannelPromise;
import io.netty.channel.ChannelHandlerContext;
import java.util.ArrayList;
import java.util.List;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.MessageToMessageEncoder;

public abstract class HttpObjectEncoder<H extends HttpMessage> extends MessageToMessageEncoder<Object>
{
    private static final int COPY_CONTENT_THRESHOLD = 128;
    static final int CRLF_SHORT = 3338;
    private static final int ZERO_CRLF_MEDIUM = 3149066;
    private static final byte[] ZERO_CRLF_CRLF;
    private static final ByteBuf CRLF_BUF;
    private static final ByteBuf ZERO_CRLF_CRLF_BUF;
    private static final float HEADERS_WEIGHT_NEW = 0.2f;
    private static final float HEADERS_WEIGHT_HISTORICAL = 0.8f;
    private static final float TRAILERS_WEIGHT_NEW = 0.2f;
    private static final float TRAILERS_WEIGHT_HISTORICAL = 0.8f;
    private static final int ST_INIT = 0;
    private static final int ST_CONTENT_NON_CHUNK = 1;
    private static final int ST_CONTENT_CHUNK = 2;
    private static final int ST_CONTENT_ALWAYS_EMPTY = 3;
    private int state;
    private float headersEncodedSizeAccumulator;
    private float trailersEncodedSizeAccumulator;
    private final List<Object> out;
    
    private static boolean checkContentState(final int state) {
        return state == 2 || state == 1 || state == 3;
    }
    
    public HttpObjectEncoder() {
        super(Object.class);
        this.state = 0;
        this.headersEncodedSizeAccumulator = 256.0f;
        this.trailersEncodedSizeAccumulator = 256.0f;
        this.out = new ArrayList<Object>();
    }
    
    @Override
    public void write(final ChannelHandlerContext ctx, final Object msg, final ChannelPromise promise) throws Exception {
        try {
            if (this.acceptOutboundMessage(msg)) {
                this.encode(ctx, msg, this.out);
                if (this.out.isEmpty()) {
                    throw new EncoderException(StringUtil.simpleClassName(this) + " must produce at least one message.");
                }
            }
            else {
                ctx.write(msg, promise);
            }
        }
        catch (final EncoderException e) {
            throw e;
        }
        catch (final Throwable t) {
            throw new EncoderException(t);
        }
        finally {
            writeOutList(ctx, this.out, promise);
        }
    }
    
    private static void writeOutList(final ChannelHandlerContext ctx, final List<Object> out, final ChannelPromise promise) {
        final int size = out.size();
        try {
            if (size == 1) {
                ctx.write(out.get(0), promise);
            }
            else if (size > 1) {
                if (promise == ctx.voidPromise()) {
                    writeVoidPromise(ctx, out);
                }
                else {
                    writePromiseCombiner(ctx, out, promise);
                }
            }
        }
        finally {
            out.clear();
        }
    }
    
    private static void writeVoidPromise(final ChannelHandlerContext ctx, final List<Object> out) {
        final ChannelPromise voidPromise = ctx.voidPromise();
        for (int i = 0; i < out.size(); ++i) {
            ctx.write(out.get(i), voidPromise);
        }
    }
    
    private static void writePromiseCombiner(final ChannelHandlerContext ctx, final List<Object> out, final ChannelPromise promise) {
        final PromiseCombiner combiner = new PromiseCombiner(ctx.executor());
        for (int i = 0; i < out.size(); ++i) {
            combiner.add(ctx.write(out.get(i)));
        }
        combiner.finish(promise);
    }
    
    @Override
    protected void encode(final ChannelHandlerContext ctx, final Object msg, final List<Object> out) throws Exception {
        if (msg == Unpooled.EMPTY_BUFFER) {
            out.add(Unpooled.EMPTY_BUFFER);
            return;
        }
        if (msg instanceof FullHttpMessage) {
            this.encodeFullHttpMessage(ctx, msg, out);
            return;
        }
        if (msg instanceof HttpMessage) {
            H m;
            try {
                m = (H)msg;
            }
            catch (final Exception rethrow) {
                ReferenceCountUtil.release(msg);
                throw rethrow;
            }
            if (m instanceof LastHttpContent) {
                this.encodeHttpMessageLastContent(ctx, m, out);
            }
            else if (m instanceof HttpContent) {
                this.encodeHttpMessageNotLastContent(ctx, m, out);
            }
            else {
                this.encodeJustHttpMessage(ctx, m, out);
            }
        }
        else {
            this.encodeNotHttpMessageContentTypes(ctx, msg, out);
        }
    }
    
    private void encodeJustHttpMessage(final ChannelHandlerContext ctx, final H m, final List<Object> out) throws Exception {
        assert !(m instanceof HttpContent);
        try {
            if (this.state != 0) {
                throwUnexpectedMessageTypeEx(m, this.state);
            }
            final ByteBuf buf = this.encodeInitHttpMessage(ctx, m);
            assert checkContentState(this.state);
            out.add(buf);
        }
        finally {
            ReferenceCountUtil.release(m);
        }
    }
    
    private void encodeByteBufHttpContent(final int state, final ChannelHandlerContext ctx, final ByteBuf buf, final ByteBuf content, final HttpHeaders trailingHeaders, final List<Object> out) {
        switch (state) {
            case 1: {
                if (encodeContentNonChunk(out, buf, content)) {
                    break;
                }
            }
            case 3: {
                out.add(buf);
                break;
            }
            case 2: {
                out.add(buf);
                this.encodeChunkedHttpContent(ctx, content, trailingHeaders, out);
                break;
            }
            default: {
                throw new Error("Unexpected http object encoder state: " + state);
            }
        }
    }
    
    private void encodeHttpMessageNotLastContent(final ChannelHandlerContext ctx, final H m, final List<Object> out) throws Exception {
        assert m instanceof HttpContent;
        assert !(m instanceof LastHttpContent);
        final HttpContent httpContent = (HttpContent)m;
        try {
            if (this.state != 0) {
                throwUnexpectedMessageTypeEx(m, this.state);
            }
            final ByteBuf buf = this.encodeInitHttpMessage(ctx, m);
            assert checkContentState(this.state);
            this.encodeByteBufHttpContent(this.state, ctx, buf, httpContent.content(), null, out);
        }
        finally {
            httpContent.release();
        }
    }
    
    private void encodeHttpMessageLastContent(final ChannelHandlerContext ctx, final H m, final List<Object> out) throws Exception {
        assert m instanceof LastHttpContent;
        final LastHttpContent httpContent = (LastHttpContent)m;
        try {
            if (this.state != 0) {
                throwUnexpectedMessageTypeEx(m, this.state);
            }
            final ByteBuf buf = this.encodeInitHttpMessage(ctx, m);
            assert checkContentState(this.state);
            this.encodeByteBufHttpContent(this.state, ctx, buf, httpContent.content(), httpContent.trailingHeaders(), out);
            this.state = 0;
        }
        finally {
            httpContent.release();
        }
    }
    
    private void encodeNotHttpMessageContentTypes(final ChannelHandlerContext ctx, final Object msg, final List<Object> out) {
        assert !(msg instanceof HttpMessage);
        if (this.state == 0) {
            try {
                if (msg instanceof ByteBuf && bypassEncoderIfEmpty((ByteBuf)msg, out)) {
                    return;
                }
                throwUnexpectedMessageTypeEx(msg, 0);
            }
            finally {
                ReferenceCountUtil.release(msg);
            }
        }
        if (msg == LastHttpContent.EMPTY_LAST_CONTENT) {
            this.state = encodeEmptyLastHttpContent(this.state, out);
            return;
        }
        if (msg instanceof LastHttpContent) {
            this.encodeLastHttpContent(ctx, (LastHttpContent)msg, out);
            return;
        }
        if (msg instanceof HttpContent) {
            this.encodeHttpContent(ctx, (HttpContent)msg, out);
            return;
        }
        if (msg instanceof ByteBuf) {
            this.encodeByteBufContent(ctx, (ByteBuf)msg, out);
            return;
        }
        if (msg instanceof FileRegion) {
            this.encodeFileRegionContent(ctx, (FileRegion)msg, out);
            return;
        }
        try {
            throwUnexpectedMessageTypeEx(msg, this.state);
        }
        finally {
            ReferenceCountUtil.release(msg);
        }
    }
    
    private void encodeFullHttpMessage(final ChannelHandlerContext ctx, final Object o, final List<Object> out) throws Exception {
        assert o instanceof FullHttpMessage;
        final FullHttpMessage msg = (FullHttpMessage)o;
        try {
            if (this.state != 0) {
                throwUnexpectedMessageTypeEx(o, this.state);
            }
            final H m = (H)o;
            final int state = this.isContentAlwaysEmpty(m) ? 3 : (HttpUtil.isTransferEncodingChunked(m) ? 2 : 1);
            final ByteBuf content = msg.content();
            final boolean accountForContentSize = content.readableBytes() > 0 && state == 1 && content.readableBytes() <= Math.max(128, (int)this.headersEncodedSizeAccumulator / 8);
            final int headersAndContentSize = (int)this.headersEncodedSizeAccumulator + (accountForContentSize ? content.readableBytes() : 0);
            final ByteBuf buf = ctx.alloc().buffer(headersAndContentSize);
            this.encodeInitialLine(buf, m);
            this.sanitizeHeadersBeforeEncode(m, state == 3);
            this.encodeHeaders(m.headers(), buf);
            ByteBufUtil.writeShortBE(buf, 3338);
            this.headersEncodedSizeAccumulator = 0.2f * padSizeForAccumulation(buf.readableBytes()) + 0.8f * this.headersEncodedSizeAccumulator;
            this.encodeByteBufHttpContent(state, ctx, buf, content, msg.trailingHeaders(), out);
        }
        finally {
            msg.release();
        }
    }
    
    private static boolean encodeContentNonChunk(final List<Object> out, final ByteBuf buf, final ByteBuf content) {
        final int contentLength = content.readableBytes();
        if (contentLength > 0) {
            if (buf.maxFastWritableBytes() >= contentLength) {
                buf.writeBytes(content);
                out.add(buf);
            }
            else {
                out.add(buf);
                out.add(content.retain());
            }
            return true;
        }
        return false;
    }
    
    private static void throwUnexpectedMessageTypeEx(final Object msg, final int state) {
        throw new IllegalStateException("unexpected message type: " + StringUtil.simpleClassName(msg) + ", state: " + state);
    }
    
    private void encodeFileRegionContent(final ChannelHandlerContext ctx, final FileRegion msg, final List<Object> out) {
        try {
            assert this.state != 0;
            switch (this.state) {
                case 1: {
                    if (msg.count() > 0L) {
                        out.add(msg.retain());
                        break;
                    }
                }
                case 3: {
                    out.add(Unpooled.EMPTY_BUFFER);
                    break;
                }
                case 2: {
                    encodedChunkedFileRegionContent(ctx, msg, out);
                    break;
                }
                default: {
                    throw new Error("Unexpected http object encoder state: " + this.state);
                }
            }
        }
        finally {
            msg.release();
        }
    }
    
    private static boolean bypassEncoderIfEmpty(final ByteBuf msg, final List<Object> out) {
        if (!msg.isReadable()) {
            out.add(msg.retain());
            return true;
        }
        return false;
    }
    
    private void encodeByteBufContent(final ChannelHandlerContext ctx, final ByteBuf content, final List<Object> out) {
        try {
            assert this.state != 0;
            if (bypassEncoderIfEmpty(content, out)) {
                return;
            }
            this.encodeByteBufAndTrailers(this.state, ctx, out, content, null);
        }
        finally {
            content.release();
        }
    }
    
    private static int encodeEmptyLastHttpContent(final int state, final List<Object> out) {
        assert state != 0;
        switch (state) {
            case 1:
            case 3: {
                out.add(Unpooled.EMPTY_BUFFER);
                break;
            }
            case 2: {
                out.add(HttpObjectEncoder.ZERO_CRLF_CRLF_BUF.duplicate());
                break;
            }
            default: {
                throw new Error("Unexpected http object encoder state: " + state);
            }
        }
        return 0;
    }
    
    private void encodeLastHttpContent(final ChannelHandlerContext ctx, final LastHttpContent msg, final List<Object> out) {
        assert this.state != 0;
        assert !(msg instanceof HttpMessage);
        try {
            this.encodeByteBufAndTrailers(this.state, ctx, out, msg.content(), msg.trailingHeaders());
            this.state = 0;
        }
        finally {
            msg.release();
        }
    }
    
    private void encodeHttpContent(final ChannelHandlerContext ctx, final HttpContent msg, final List<Object> out) {
        assert this.state != 0;
        assert !(msg instanceof HttpMessage);
        assert !(msg instanceof LastHttpContent);
        try {
            this.encodeByteBufAndTrailers(this.state, ctx, out, msg.content(), null);
        }
        finally {
            msg.release();
        }
    }
    
    private void encodeByteBufAndTrailers(final int state, final ChannelHandlerContext ctx, final List<Object> out, final ByteBuf content, final HttpHeaders trailingHeaders) {
        switch (state) {
            case 1: {
                if (content.isReadable()) {
                    out.add(content.retain());
                    break;
                }
            }
            case 3: {
                out.add(Unpooled.EMPTY_BUFFER);
                break;
            }
            case 2: {
                this.encodeChunkedHttpContent(ctx, content, trailingHeaders, out);
                break;
            }
            default: {
                throw new Error("Unexpected http object encoder state: " + state);
            }
        }
    }
    
    private void encodeChunkedHttpContent(final ChannelHandlerContext ctx, final ByteBuf content, final HttpHeaders trailingHeaders, final List<Object> out) {
        final int contentLength = content.readableBytes();
        if (contentLength > 0) {
            addEncodedLengthHex(ctx, contentLength, out);
            out.add(content.retain());
            out.add(HttpObjectEncoder.CRLF_BUF.duplicate());
        }
        if (trailingHeaders != null) {
            this.encodeTrailingHeaders(ctx, trailingHeaders, out);
        }
        else if (contentLength == 0) {
            out.add(content.retain());
        }
    }
    
    private void encodeTrailingHeaders(final ChannelHandlerContext ctx, final HttpHeaders trailingHeaders, final List<Object> out) {
        if (trailingHeaders.isEmpty()) {
            out.add(HttpObjectEncoder.ZERO_CRLF_CRLF_BUF.duplicate());
        }
        else {
            final ByteBuf buf = ctx.alloc().buffer((int)this.trailersEncodedSizeAccumulator);
            ByteBufUtil.writeMediumBE(buf, 3149066);
            this.encodeHeaders(trailingHeaders, buf);
            ByteBufUtil.writeShortBE(buf, 3338);
            this.trailersEncodedSizeAccumulator = 0.2f * padSizeForAccumulation(buf.readableBytes()) + 0.8f * this.trailersEncodedSizeAccumulator;
            out.add(buf);
        }
    }
    
    private ByteBuf encodeInitHttpMessage(final ChannelHandlerContext ctx, final H m) throws Exception {
        assert this.state == 0;
        final ByteBuf buf = ctx.alloc().buffer((int)this.headersEncodedSizeAccumulator);
        this.encodeInitialLine(buf, m);
        this.state = (this.isContentAlwaysEmpty(m) ? 3 : (HttpUtil.isTransferEncodingChunked(m) ? 2 : 1));
        this.sanitizeHeadersBeforeEncode(m, this.state == 3);
        this.encodeHeaders(m.headers(), buf);
        ByteBufUtil.writeShortBE(buf, 3338);
        this.headersEncodedSizeAccumulator = 0.2f * padSizeForAccumulation(buf.readableBytes()) + 0.8f * this.headersEncodedSizeAccumulator;
        return buf;
    }
    
    protected void encodeHeaders(final HttpHeaders headers, final ByteBuf buf) {
        final Iterator<Map.Entry<CharSequence, CharSequence>> iter = headers.iteratorCharSequence();
        while (iter.hasNext()) {
            final Map.Entry<CharSequence, CharSequence> header = iter.next();
            HttpHeadersEncoder.encoderHeader(header.getKey(), header.getValue(), buf);
        }
    }
    
    private static void encodedChunkedFileRegionContent(final ChannelHandlerContext ctx, final FileRegion msg, final List<Object> out) {
        final long contentLength = msg.count();
        if (contentLength > 0L) {
            addEncodedLengthHex(ctx, contentLength, out);
            out.add(msg.retain());
            out.add(HttpObjectEncoder.CRLF_BUF.duplicate());
        }
        else if (contentLength == 0L) {
            out.add(msg.retain());
        }
    }
    
    private static void addEncodedLengthHex(final ChannelHandlerContext ctx, final long contentLength, final List<Object> out) {
        final String lengthHex = Long.toHexString(contentLength);
        final ByteBuf buf = ctx.alloc().buffer(lengthHex.length() + 2);
        buf.writeCharSequence(lengthHex, CharsetUtil.US_ASCII);
        ByteBufUtil.writeShortBE(buf, 3338);
        out.add(buf);
    }
    
    protected void sanitizeHeadersBeforeEncode(final H msg, final boolean isAlwaysEmpty) {
    }
    
    protected boolean isContentAlwaysEmpty(final H msg) {
        return false;
    }
    
    @Override
    public boolean acceptOutboundMessage(final Object msg) throws Exception {
        return msg == Unpooled.EMPTY_BUFFER || msg == LastHttpContent.EMPTY_LAST_CONTENT || msg instanceof FullHttpMessage || msg instanceof HttpMessage || msg instanceof LastHttpContent || msg instanceof HttpContent || msg instanceof ByteBuf || msg instanceof FileRegion;
    }
    
    private static int padSizeForAccumulation(final int readableBytes) {
        return (readableBytes << 2) / 3;
    }
    
    @Deprecated
    protected static void encodeAscii(final String s, final ByteBuf buf) {
        buf.writeCharSequence(s, CharsetUtil.US_ASCII);
    }
    
    protected abstract void encodeInitialLine(final ByteBuf p0, final H p1) throws Exception;
    
    static {
        ZERO_CRLF_CRLF = new byte[] { 48, 13, 10, 13, 10 };
        CRLF_BUF = LeakPresenceDetector.staticInitializer(() -> Unpooled.unreleasableBuffer(Unpooled.directBuffer(2).writeByte(13).writeByte(10)).asReadOnly());
        ZERO_CRLF_CRLF_BUF = LeakPresenceDetector.staticInitializer(() -> Unpooled.unreleasableBuffer(Unpooled.directBuffer(HttpObjectEncoder.ZERO_CRLF_CRLF.length).writeBytes(HttpObjectEncoder.ZERO_CRLF_CRLF)).asReadOnly());
    }
}
