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

package io.netty.handler.codec.http3;

import java.util.Arrays;
import io.netty.util.ReferenceCountUtil;
import java.util.Objects;
import io.netty.handler.codec.quic.QuicStreamChannel;
import java.util.Iterator;
import java.util.ArrayDeque;
import java.util.Map;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.ByteBuf;
import java.util.Queue;
import io.netty.util.collection.LongObjectHashMap;

final class QpackEncoder
{
    private static final QpackException INVALID_SECTION_ACKNOWLEDGMENT;
    private static final int DYNAMIC_TABLE_ENCODE_NOT_DONE = -1;
    private static final int DYNAMIC_TABLE_ENCODE_NOT_POSSIBLE = -2;
    private final QpackHuffmanEncoder huffmanEncoder;
    private final QpackEncoderDynamicTable dynamicTable;
    private int maxBlockedStreams;
    private int blockedStreams;
    private LongObjectHashMap<Queue<Indices>> streamSectionTrackers;
    
    QpackEncoder() {
        this(new QpackEncoderDynamicTable());
    }
    
    QpackEncoder(final QpackEncoderDynamicTable dynamicTable) {
        this.huffmanEncoder = new QpackHuffmanEncoder();
        this.dynamicTable = dynamicTable;
    }
    
    void encodeHeaders(final QpackAttributes qpackAttributes, final ByteBuf out, final ByteBufAllocator allocator, final long streamId, final Http3Headers headers) {
        final int base = this.dynamicTable.insertCount();
        final ByteBuf tmp = allocator.buffer();
        try {
            int maxDynamicTblIdx = -1;
            int requiredInsertCount = 0;
            Indices dynamicTableIndices = null;
            for (final Map.Entry<CharSequence, CharSequence> header : headers) {
                final CharSequence name = header.getKey();
                final CharSequence value = header.getValue();
                final int dynamicTblIdx = this.encodeHeader(qpackAttributes, tmp, base, name, value);
                if (dynamicTblIdx >= 0) {
                    final int req = this.dynamicTable.addReferenceToEntry(name, value, dynamicTblIdx);
                    if (dynamicTblIdx > maxDynamicTblIdx) {
                        maxDynamicTblIdx = dynamicTblIdx;
                        requiredInsertCount = req;
                    }
                    if (dynamicTableIndices == null) {
                        dynamicTableIndices = new Indices();
                    }
                    dynamicTableIndices.add(dynamicTblIdx);
                }
            }
            if (dynamicTableIndices != null) {
                assert this.streamSectionTrackers != null;
                this.streamSectionTrackers.computeIfAbsent(Long.valueOf(streamId), __ -> new ArrayDeque()).add(dynamicTableIndices);
            }
            QpackUtil.encodePrefixedInteger(out, (byte)0, 8, this.dynamicTable.encodedRequiredInsertCount(requiredInsertCount));
            if (base >= requiredInsertCount) {
                QpackUtil.encodePrefixedInteger(out, (byte)0, 7, base - requiredInsertCount);
            }
            else {
                QpackUtil.encodePrefixedInteger(out, (byte)(-128), 7, requiredInsertCount - base - 1);
            }
            out.writeBytes(tmp);
        }
        finally {
            tmp.release();
        }
    }
    
    void configureDynamicTable(final QpackAttributes attributes, final long maxTableCapacity, final int blockedStreams) throws QpackException {
        if (maxTableCapacity > 0L) {
            assert attributes.encoderStreamAvailable();
            final QuicStreamChannel encoderStream = attributes.encoderStream();
            this.dynamicTable.maxTableCapacity(maxTableCapacity);
            final ByteBuf tableCapacity = encoderStream.alloc().buffer(8);
            QpackUtil.encodePrefixedInteger(tableCapacity, (byte)32, 5, maxTableCapacity);
            Http3CodecUtils.closeOnFailure(encoderStream.writeAndFlush(tableCapacity));
            this.streamSectionTrackers = new LongObjectHashMap<Queue<Indices>>();
            this.maxBlockedStreams = blockedStreams;
        }
    }
    
    void sectionAcknowledgment(final long streamId) throws QpackException {
        assert this.streamSectionTrackers != null;
        final Queue<Indices> tracker = this.streamSectionTrackers.get(streamId);
        if (tracker == null) {
            throw QpackEncoder.INVALID_SECTION_ACKNOWLEDGMENT;
        }
        final Indices dynamicTableIndices = tracker.poll();
        if (tracker.isEmpty()) {
            this.streamSectionTrackers.remove(streamId);
        }
        if (dynamicTableIndices == null) {
            throw QpackEncoder.INVALID_SECTION_ACKNOWLEDGMENT;
        }
        final Indices indices = dynamicTableIndices;
        final QpackEncoderDynamicTable dynamicTable = this.dynamicTable;
        Objects.requireNonNull(dynamicTable);
        indices.forEach(dynamicTable::acknowledgeInsertCountOnAck);
    }
    
    void streamCancellation(final long streamId) throws QpackException {
        if (this.streamSectionTrackers == null) {
            return;
        }
        final Queue<Indices> tracker = this.streamSectionTrackers.remove(streamId);
        if (tracker != null) {
            while (true) {
                final Indices dynamicTableIndices = tracker.poll();
                if (dynamicTableIndices == null) {
                    break;
                }
                final Indices indices = dynamicTableIndices;
                final QpackEncoderDynamicTable dynamicTable = this.dynamicTable;
                Objects.requireNonNull(dynamicTable);
                indices.forEach(dynamicTable::acknowledgeInsertCountOnCancellation);
            }
        }
    }
    
    void insertCountIncrement(final int increment) throws QpackException {
        this.dynamicTable.incrementKnownReceivedCount(increment);
    }
    
    private int encodeHeader(final QpackAttributes qpackAttributes, final ByteBuf out, final int base, final CharSequence name, final CharSequence value) {
        final int index = QpackStaticTable.findFieldIndex(name, value);
        if (index != -1) {
            if ((index & 0x400) == 0x400) {
                int dynamicTblIdx = this.tryEncodeWithDynamicTable(qpackAttributes, out, base, name, value);
                if (dynamicTblIdx >= 0) {
                    return dynamicTblIdx;
                }
                final int nameIdx = index ^ 0x400;
                dynamicTblIdx = this.tryAddToDynamicTable(qpackAttributes, true, nameIdx, name, value);
                if (dynamicTblIdx >= 0) {
                    if (dynamicTblIdx >= base) {
                        this.encodePostBaseIndexed(out, base, dynamicTblIdx);
                    }
                    else {
                        this.encodeIndexedDynamicTable(out, base, dynamicTblIdx);
                    }
                    return dynamicTblIdx;
                }
                this.encodeLiteralWithNameRefStaticTable(out, nameIdx, value);
            }
            else {
                this.encodeIndexedStaticTable(out, index);
            }
            return qpackAttributes.dynamicTableDisabled() ? -2 : -1;
        }
        if (qpackAttributes.dynamicTableDisabled()) {
            this.encodeLiteral(out, name, value);
            return -2;
        }
        return this.encodeWithDynamicTable(qpackAttributes, out, base, name, value);
    }
    
    private int encodeWithDynamicTable(final QpackAttributes qpackAttributes, final ByteBuf out, final int base, final CharSequence name, final CharSequence value) {
        int idx = this.tryEncodeWithDynamicTable(qpackAttributes, out, base, name, value);
        if (idx >= 0) {
            return idx;
        }
        if (idx == -1) {
            idx = this.tryAddToDynamicTable(qpackAttributes, false, -1, name, value);
            if (idx >= 0) {
                if (idx >= base) {
                    this.encodePostBaseIndexed(out, base, idx);
                }
                else {
                    this.encodeIndexedDynamicTable(out, base, idx);
                }
                return idx;
            }
        }
        this.encodeLiteral(out, name, value);
        return idx;
    }
    
    private int tryEncodeWithDynamicTable(final QpackAttributes qpackAttributes, final ByteBuf out, final int base, final CharSequence name, final CharSequence value) {
        if (qpackAttributes.dynamicTableDisabled()) {
            return -2;
        }
        assert qpackAttributes.encoderStreamAvailable();
        final QuicStreamChannel encoderStream = qpackAttributes.encoderStream();
        int idx = this.dynamicTable.getEntryIndex(name, value);
        if (idx == Integer.MIN_VALUE) {
            return -1;
        }
        if (idx >= 0) {
            if (this.dynamicTable.requiresDuplication(idx, QpackHeaderField.sizeOf(name, value))) {
                idx = this.dynamicTable.add(name, value, QpackHeaderField.sizeOf(name, value));
                assert idx >= 0;
                final ByteBuf duplicate = encoderStream.alloc().buffer(8);
                QpackUtil.encodePrefixedInteger(duplicate, (byte)0, 5, this.dynamicTable.relativeIndexForEncoderInstructions(idx));
                Http3CodecUtils.closeOnFailure(encoderStream.writeAndFlush(duplicate));
                if (this.mayNotBlockStream()) {
                    return -2;
                }
            }
            if (idx >= base) {
                this.encodePostBaseIndexed(out, base, idx);
            }
            else {
                this.encodeIndexedDynamicTable(out, base, idx);
            }
        }
        else {
            idx = -(idx + 1);
            final int addIdx = this.tryAddToDynamicTable(qpackAttributes, false, this.dynamicTable.relativeIndexForEncoderInstructions(idx), name, value);
            if (addIdx < 0) {
                return -2;
            }
            idx = addIdx;
            if (idx >= base) {
                this.encodeLiteralWithPostBaseNameRef(out, base, idx, value);
            }
            else {
                this.encodeLiteralWithNameRefDynamicTable(out, base, idx, value);
            }
        }
        return idx;
    }
    
    private int tryAddToDynamicTable(final QpackAttributes qpackAttributes, final boolean staticTableNameRef, final int nameIdx, final CharSequence name, final CharSequence value) {
        if (qpackAttributes.dynamicTableDisabled()) {
            return -2;
        }
        assert qpackAttributes.encoderStreamAvailable();
        final QuicStreamChannel encoderStream = qpackAttributes.encoderStream();
        final int idx = this.dynamicTable.add(name, value, QpackHeaderField.sizeOf(name, value));
        if (idx >= 0) {
            ByteBuf insert = null;
            try {
                if (nameIdx >= 0) {
                    insert = encoderStream.alloc().buffer(value.length() + 16);
                    QpackUtil.encodePrefixedInteger(insert, (byte)(staticTableNameRef ? 192 : 128), 6, nameIdx);
                }
                else {
                    insert = encoderStream.alloc().buffer(name.length() + value.length() + 16);
                    this.encodeLengthPrefixedHuffmanEncodedLiteral(insert, (byte)96, 5, name);
                }
                this.encodeStringLiteral(insert, value);
            }
            catch (final Exception e) {
                ReferenceCountUtil.release(insert);
                return -1;
            }
            Http3CodecUtils.closeOnFailure(encoderStream.writeAndFlush(insert));
            if (this.mayNotBlockStream()) {
                return -1;
            }
            ++this.blockedStreams;
        }
        return idx;
    }
    
    private void encodeIndexedStaticTable(final ByteBuf out, final int index) {
        QpackUtil.encodePrefixedInteger(out, (byte)(-64), 6, index);
    }
    
    private void encodeIndexedDynamicTable(final ByteBuf out, final int base, final int index) {
        QpackUtil.encodePrefixedInteger(out, (byte)(-128), 6, base - index - 1);
    }
    
    private void encodePostBaseIndexed(final ByteBuf out, final int base, final int index) {
        QpackUtil.encodePrefixedInteger(out, (byte)16, 4, index - base);
    }
    
    private void encodeLiteralWithNameRefStaticTable(final ByteBuf out, final int nameIndex, final CharSequence value) {
        QpackUtil.encodePrefixedInteger(out, (byte)80, 4, nameIndex);
        this.encodeStringLiteral(out, value);
    }
    
    private void encodeLiteralWithNameRefDynamicTable(final ByteBuf out, final int base, final int nameIndex, final CharSequence value) {
        QpackUtil.encodePrefixedInteger(out, (byte)80, 4, base - nameIndex - 1);
        this.encodeStringLiteral(out, value);
    }
    
    private void encodeLiteralWithPostBaseNameRef(final ByteBuf out, final int base, final int nameIndex, final CharSequence value) {
        QpackUtil.encodePrefixedInteger(out, (byte)0, 4, nameIndex - base);
        this.encodeStringLiteral(out, value);
    }
    
    private void encodeLiteral(final ByteBuf out, final CharSequence name, final CharSequence value) {
        this.encodeLengthPrefixedHuffmanEncodedLiteral(out, (byte)40, 3, name);
        this.encodeStringLiteral(out, value);
    }
    
    private void encodeStringLiteral(final ByteBuf out, final CharSequence value) {
        this.encodeLengthPrefixedHuffmanEncodedLiteral(out, (byte)(-128), 7, value);
    }
    
    private void encodeLengthPrefixedHuffmanEncodedLiteral(final ByteBuf out, final byte mask, final int prefix, final CharSequence value) {
        final int huffmanLength = this.huffmanEncoder.getEncodedLength(value);
        QpackUtil.encodePrefixedInteger(out, mask, prefix, huffmanLength);
        this.huffmanEncoder.encode(out, value);
    }
    
    private boolean mayNotBlockStream() {
        return this.blockedStreams >= this.maxBlockedStreams - 1;
    }
    
    static {
        INVALID_SECTION_ACKNOWLEDGMENT = QpackException.newStatic(QpackDecoder.class, "sectionAcknowledgment(...)", "QPACK - section acknowledgment received for unknown stream.");
    }
    
    private static final class Indices
    {
        private int idx;
        private int[] array;
        
        private Indices() {
            this.array = new int[4];
        }
        
        void add(final int index) {
            if (this.idx == this.array.length) {
                this.array = Arrays.copyOf(this.array, this.array.length << 1);
            }
            this.array[this.idx++] = index;
        }
        
        void forEach(final IndexConsumer consumer) throws QpackException {
            for (int i = 0; i < this.idx; ++i) {
                consumer.accept(this.array[i]);
            }
        }
        
        @FunctionalInterface
        interface IndexConsumer
        {
            void accept(final int p0) throws QpackException;
        }
    }
}
