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

package io.netty.handler.codec.quic;

import io.netty.channel.DefaultAddressedEnvelope;
import java.util.concurrent.atomic.AtomicBoolean;
import java.nio.ByteBuffer;
import org.jetbrains.annotations.Nullable;
import io.netty.channel.Channel;
import io.netty.buffer.ByteBuf;
import io.netty.channel.socket.DatagramPacket;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.internal.ObjectUtil;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.List;
import io.netty.channel.ChannelInboundHandlerAdapter;

public abstract class QuicCodecDispatcher extends ChannelInboundHandlerAdapter
{
    private static final int MAX_LOCAL_CONNECTION_ID_LENGTH = 20;
    private final List<ChannelHandlerContextDispatcher> contextList;
    private final int localConnectionIdLength;
    
    protected QuicCodecDispatcher() {
        this(20);
    }
    
    protected QuicCodecDispatcher(final int localConnectionIdLength) {
        this.contextList = new CopyOnWriteArrayList<ChannelHandlerContextDispatcher>();
        this.localConnectionIdLength = ObjectUtil.checkInRange(localConnectionIdLength, 10, 20, "localConnectionIdLength");
    }
    
    @Override
    public final boolean isSharable() {
        return true;
    }
    
    @Override
    public final void handlerAdded(final ChannelHandlerContext ctx) throws Exception {
        super.handlerAdded(ctx);
        final ChannelHandlerContextDispatcher ctxDispatcher = new ChannelHandlerContextDispatcher(ctx);
        this.contextList.add(ctxDispatcher);
        final int idx = this.contextList.indexOf(ctxDispatcher);
        try {
            final QuicConnectionIdGenerator idGenerator = this.newIdGenerator((short)idx);
            this.initChannel(ctx.channel(), this.localConnectionIdLength, idGenerator);
        }
        catch (final Exception e) {
            this.contextList.set(idx, null);
            throw e;
        }
    }
    
    @Override
    public final void handlerRemoved(final ChannelHandlerContext ctx) throws Exception {
        super.handlerRemoved(ctx);
        for (int idx = 0; idx < this.contextList.size(); ++idx) {
            final ChannelHandlerContextDispatcher ctxDispatcher = this.contextList.get(idx);
            if (ctxDispatcher != null && ctxDispatcher.ctx.equals(ctx)) {
                this.contextList.set(idx, null);
                break;
            }
        }
    }
    
    @Override
    public final void channelRead(final ChannelHandlerContext ctx, final Object msg) throws Exception {
        final DatagramPacket packet = (DatagramPacket)msg;
        final ByteBuf connectionId = getDestinationConnectionId(((DefaultAddressedEnvelope<ByteBuf, A>)packet).content(), this.localConnectionIdLength);
        if (connectionId != null) {
            final int idx = this.decodeIndex(connectionId);
            if (this.contextList.size() > idx) {
                final ChannelHandlerContextDispatcher selectedCtx = this.contextList.get(idx);
                if (selectedCtx != null) {
                    selectedCtx.fireChannelRead(msg);
                    return;
                }
            }
        }
        ctx.fireChannelRead(msg);
    }
    
    @Override
    public final void channelReadComplete(final ChannelHandlerContext ctx) {
        boolean dispatchForOwnContextAlready = false;
        for (int i = 0; i < this.contextList.size(); ++i) {
            final ChannelHandlerContextDispatcher ctxDispatcher = this.contextList.get(i);
            if (ctxDispatcher != null) {
                final boolean fired = ctxDispatcher.fireChannelReadCompleteIfNeeded();
                if (fired && !dispatchForOwnContextAlready) {
                    dispatchForOwnContextAlready = ctx.equals(ctxDispatcher.ctx);
                }
            }
        }
        if (!dispatchForOwnContextAlready) {
            ctx.fireChannelReadComplete();
        }
    }
    
    protected abstract void initChannel(final Channel p0, final int p1, final QuicConnectionIdGenerator p2) throws Exception;
    
    protected int decodeIndex(final ByteBuf connectionId) {
        return decodeIdx(connectionId);
    }
    
    @Nullable
    static ByteBuf getDestinationConnectionId(final ByteBuf buffer, final int localConnectionIdLength) throws QuicException {
        if (buffer.readableBytes() > 1) {
            int offset = buffer.readerIndex();
            final boolean shortHeader = hasShortHeader(buffer);
            ++offset;
            if (shortHeader) {
                return QuicHeaderParser.sliceCid(buffer, offset, localConnectionIdLength);
            }
        }
        return null;
    }
    
    static boolean hasShortHeader(final ByteBuf buffer) {
        return QuicHeaderParser.hasShortHeader(buffer.getByte(buffer.readerIndex()));
    }
    
    static int decodeIdx(final ByteBuf connectionId) {
        if (connectionId.readableBytes() >= 2) {
            return connectionId.getUnsignedShort(connectionId.readerIndex());
        }
        return -1;
    }
    
    static ByteBuffer encodeIdx(final ByteBuffer buffer, final int idx) {
        final ByteBuffer b = ByteBuffer.allocate(buffer.capacity() + 2);
        b.putShort((short)idx).put(buffer).flip();
        return b;
    }
    
    protected QuicConnectionIdGenerator newIdGenerator(final int idx) {
        return new IndexAwareQuicConnectionIdGenerator(idx, SecureRandomQuicConnectionIdGenerator.INSTANCE);
    }
    
    private static final class IndexAwareQuicConnectionIdGenerator implements QuicConnectionIdGenerator
    {
        private final int idx;
        private final QuicConnectionIdGenerator idGenerator;
        
        IndexAwareQuicConnectionIdGenerator(final int idx, final QuicConnectionIdGenerator idGenerator) {
            this.idx = idx;
            this.idGenerator = idGenerator;
        }
        
        @Override
        public ByteBuffer newId(final int length) {
            if (length > 2) {
                return QuicCodecDispatcher.encodeIdx(this.idGenerator.newId(length - 2), this.idx);
            }
            return this.idGenerator.newId(length);
        }
        
        @Override
        public ByteBuffer newId(final ByteBuffer input, final int length) {
            if (length > 2) {
                return QuicCodecDispatcher.encodeIdx(this.idGenerator.newId(input, length - 2), this.idx);
            }
            return this.idGenerator.newId(input, length);
        }
        
        @Override
        public ByteBuffer newId(final ByteBuffer scid, final ByteBuffer dcid, final int length) {
            if (length > 2) {
                return QuicCodecDispatcher.encodeIdx(this.idGenerator.newId(scid, dcid, length - 2), this.idx);
            }
            return this.idGenerator.newId(scid, dcid, length);
        }
        
        @Override
        public int maxConnectionIdLength() {
            return this.idGenerator.maxConnectionIdLength();
        }
        
        @Override
        public boolean isIdempotent() {
            return false;
        }
    }
    
    private static final class ChannelHandlerContextDispatcher extends AtomicBoolean
    {
        private final ChannelHandlerContext ctx;
        
        ChannelHandlerContextDispatcher(final ChannelHandlerContext ctx) {
            this.ctx = ctx;
        }
        
        void fireChannelRead(final Object msg) {
            this.ctx.fireChannelRead(msg);
            this.set(true);
        }
        
        boolean fireChannelReadCompleteIfNeeded() {
            if (this.getAndSet(false)) {
                this.ctx.fireChannelReadComplete();
                return true;
            }
            return false;
        }
    }
}
