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

package io.netty.handler.codec.http3;

import io.netty.channel.Channel;
import io.netty.channel.socket.ChannelInputShutdownReadComplete;
import io.netty.handler.codec.quic.QuicStreamChannelBootstrap;
import java.util.function.UnaryOperator;
import io.netty.handler.codec.quic.QuicStreamType;
import io.netty.util.concurrent.Promise;
import io.netty.util.concurrent.Future;
import org.jetbrains.annotations.Nullable;
import io.netty.channel.ChannelHandler;
import io.netty.util.ReferenceCountUtil;
import io.netty.handler.codec.quic.QuicStreamChannel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.internal.PlatformDependent;
import java.util.Objects;
import io.netty.channel.ChannelInboundHandler;
import java.util.concurrent.ConcurrentMap;
import io.netty.handler.codec.quic.QuicChannel;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;

public final class Http3ServerPushStreamManager
{
    private static final AtomicLongFieldUpdater<Http3ServerPushStreamManager> nextIdUpdater;
    private static final Object CANCELLED_STREAM;
    private static final Object PUSH_ID_GENERATED;
    private static final Object AWAITING_STREAM_ESTABLISHMENT;
    private final QuicChannel channel;
    private final ConcurrentMap<Long, Object> pushStreams;
    private final ChannelInboundHandler controlStreamListener;
    private volatile long nextId;
    
    public Http3ServerPushStreamManager(final QuicChannel channel) {
        this(channel, 8);
    }
    
    public Http3ServerPushStreamManager(final QuicChannel channel, final int initialPushStreamsCountHint) {
        this.channel = Objects.requireNonNull(channel, "channel");
        this.pushStreams = PlatformDependent.newConcurrentHashMap(initialPushStreamsCountHint);
        this.controlStreamListener = new ChannelInboundHandlerAdapter() {
            static final /* synthetic */ boolean $assertionsDisabled;
            
            @Override
            public void channelRead(final ChannelHandlerContext ctx, final Object msg) {
                if (msg instanceof Http3CancelPushFrame) {
                    final long pushId = ((Http3CancelPushFrame)msg).id();
                    if (pushId >= Http3ServerPushStreamManager.this.nextId) {
                        Http3CodecUtils.connectionError(ctx, Http3ErrorCode.H3_ID_ERROR, "CANCEL_PUSH id greater than the last known id", true);
                        return;
                    }
                    Http3ServerPushStreamManager.this.pushStreams.computeIfPresent(pushId, (id, existing) -> {
                        if (existing == Http3ServerPushStreamManager.AWAITING_STREAM_ESTABLISHMENT) {
                            return Http3ServerPushStreamManager.CANCELLED_STREAM;
                        }
                        else if (existing == Http3ServerPushStreamManager.PUSH_ID_GENERATED) {
                            new IllegalStateException("Unexpected push stream state " + existing + " for pushId: " + id);
                            throw;
                        }
                        else if (!Http3ServerPushStreamManager$1.$assertionsDisabled && !(existing instanceof QuicStreamChannel)) {
                            throw new AssertionError();
                        }
                        else {
                            ((QuicStreamChannel)existing).close();
                            return null;
                        }
                    });
                }
                ReferenceCountUtil.release(msg);
            }
        };
    }
    
    public boolean isPushAllowed() {
        return this.isPushAllowed(Http3.maxPushIdReceived(this.channel));
    }
    
    public long reserveNextPushId() {
        final long maxPushId = Http3.maxPushIdReceived(this.channel);
        if (this.isPushAllowed(maxPushId)) {
            return this.nextPushId();
        }
        throw new IllegalStateException("MAX allowed push ID: " + maxPushId + ", next push ID: " + this.nextId);
    }
    
    public Future<QuicStreamChannel> newPushStream(final long pushId, @Nullable final ChannelHandler handler) {
        final Promise<QuicStreamChannel> promise = this.channel.eventLoop().newPromise();
        this.newPushStream(pushId, handler, promise);
        return promise;
    }
    
    public void newPushStream(final long pushId, @Nullable final ChannelHandler handler, final Promise<QuicStreamChannel> promise) {
        this.validatePushId(pushId);
        this.channel.createStream(QuicStreamType.UNIDIRECTIONAL, this.pushStreamInitializer(pushId, handler), promise);
        setupCancelPushIfStreamCreationFails(pushId, promise, this.channel);
    }
    
    public void newPushStream(final long pushId, @Nullable final ChannelHandler handler, final UnaryOperator<QuicStreamChannelBootstrap> bootstrapConfigurator, final Promise<QuicStreamChannel> promise) {
        this.validatePushId(pushId);
        final QuicStreamChannelBootstrap bootstrap = bootstrapConfigurator.apply(this.channel.newStreamBootstrap());
        bootstrap.type(QuicStreamType.UNIDIRECTIONAL).handler(this.pushStreamInitializer(pushId, handler)).create(promise);
        setupCancelPushIfStreamCreationFails(pushId, promise, this.channel);
    }
    
    public ChannelInboundHandler controlStreamListener() {
        return this.controlStreamListener;
    }
    
    private boolean isPushAllowed(final long maxPushId) {
        return this.nextId <= maxPushId;
    }
    
    private long nextPushId() {
        final long pushId = Http3ServerPushStreamManager.nextIdUpdater.getAndIncrement(this);
        this.pushStreams.put(pushId, Http3ServerPushStreamManager.PUSH_ID_GENERATED);
        return pushId;
    }
    
    private void validatePushId(final long pushId) {
        if (!this.pushStreams.replace(pushId, Http3ServerPushStreamManager.PUSH_ID_GENERATED, Http3ServerPushStreamManager.AWAITING_STREAM_ESTABLISHMENT)) {
            throw new IllegalArgumentException("Unknown push ID: " + pushId);
        }
    }
    
    private Http3PushStreamServerInitializer pushStreamInitializer(final long pushId, @Nullable final ChannelHandler handler) {
        Http3PushStreamServerInitializer initializer;
        if (handler instanceof Http3PushStreamServerInitializer) {
            initializer = (Http3PushStreamServerInitializer)handler;
        }
        else {
            initializer = null;
        }
        return new Http3PushStreamServerInitializer(pushId) {
            @Override
            protected void initPushStream(final QuicStreamChannel ch) {
                ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                    private boolean stateUpdated;
                    
                    @Override
                    public void channelActive(final ChannelHandlerContext ctx) {
                        if (!this.stateUpdated) {
                            this.updatePushStreamsMap();
                        }
                    }
                    
                    @Override
                    public void handlerAdded(final ChannelHandlerContext ctx) {
                        if (!this.stateUpdated && ctx.channel().isActive()) {
                            this.updatePushStreamsMap();
                        }
                    }
                    
                    private void updatePushStreamsMap() {
                        assert !this.stateUpdated;
                        this.stateUpdated = true;
                        Http3ServerPushStreamManager.this.pushStreams.compute(pushId, (id, existing) -> {
                            final Object val$ch = ch;
                            if (existing == Http3ServerPushStreamManager.AWAITING_STREAM_ESTABLISHMENT) {
                                return ch;
                            }
                            else if (existing == Http3ServerPushStreamManager.CANCELLED_STREAM) {
                                ch.close();
                                return null;
                            }
                            else {
                                new IllegalStateException("Unexpected push stream state " + existing + " for pushId: " + id);
                                throw;
                            }
                        });
                    }
                    
                    @Override
                    public void userEventTriggered(final ChannelHandlerContext ctx, final Object evt) {
                        if (evt == ChannelInputShutdownReadComplete.INSTANCE) {
                            Http3ServerPushStreamManager.this.pushStreams.remove(pushId);
                        }
                        ctx.fireUserEventTriggered(evt);
                    }
                });
                if (initializer != null) {
                    initializer.initPushStream(ch);
                }
                else if (handler != null) {
                    ch.pipeline().addLast(handler);
                }
            }
        };
    }
    
    private static void setupCancelPushIfStreamCreationFails(final long pushId, final Future<QuicStreamChannel> future, final QuicChannel channel) {
        if (future.isDone()) {
            sendCancelPushIfFailed(future, pushId, channel);
        }
        else {
            future.addListener(f -> sendCancelPushIfFailed(future, pushId, channel));
        }
    }
    
    private static void sendCancelPushIfFailed(final Future<QuicStreamChannel> future, final long pushId, final QuicChannel channel) {
        if (!future.isSuccess()) {
            final QuicStreamChannel localControlStream = Http3.getLocalControlStream(channel);
            assert localControlStream != null;
            localControlStream.writeAndFlush(new DefaultHttp3CancelPushFrame(pushId));
        }
    }
    
    static {
        nextIdUpdater = AtomicLongFieldUpdater.newUpdater(Http3ServerPushStreamManager.class, "nextId");
        CANCELLED_STREAM = new Object();
        PUSH_ID_GENERATED = new Object();
        AWAITING_STREAM_ESTABLISHMENT = new Object();
    }
}
