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

package io.netty.handler.codec.http3;

import org.jetbrains.annotations.Nullable;
import io.netty.util.AsciiString;
import io.netty.util.internal.MathUtil;

final class QpackEncoderDynamicTable
{
    private static final QpackException INVALID_KNOW_RECEIVED_COUNT_INCREMENT;
    private static final QpackException INVALID_REQUIRED_INSERT_COUNT_INCREMENT;
    private static final QpackException INVALID_TABLE_CAPACITY;
    private static final QpackException CAPACITY_ALREADY_SET;
    public static final int NOT_FOUND = Integer.MIN_VALUE;
    private final HeaderEntry[] fields;
    private final int expectedFreeCapacityPercentage;
    private final byte hashMask;
    private long size;
    private long maxTableCapacity;
    private final HeaderEntry head;
    private HeaderEntry drain;
    private HeaderEntry knownReceived;
    private HeaderEntry tail;
    
    QpackEncoderDynamicTable() {
        this(16, 10);
    }
    
    QpackEncoderDynamicTable(final int arraySizeHint, final int expectedFreeCapacityPercentage) {
        this.maxTableCapacity = -1L;
        this.fields = new HeaderEntry[MathUtil.findNextPositivePowerOfTwo(Math.max(2, Math.min(arraySizeHint, 128)))];
        this.hashMask = (byte)(this.fields.length - 1);
        this.head = new HeaderEntry(-1, AsciiString.EMPTY_STRING, AsciiString.EMPTY_STRING, -1, null);
        this.expectedFreeCapacityPercentage = expectedFreeCapacityPercentage;
        this.resetIndicesToHead();
    }
    
    int add(final CharSequence name, final CharSequence value, final long headerSize) {
        if (this.maxTableCapacity - this.size < headerSize) {
            return -1;
        }
        if (this.tail.index == Integer.MAX_VALUE) {
            this.evictUnreferencedEntries();
            return -1;
        }
        final int h = AsciiString.hashCode(name);
        final int i = this.index(h);
        final HeaderEntry old = this.fields[i];
        final HeaderEntry e = new HeaderEntry(h, name, value, this.tail.index + 1, old);
        (this.fields[i] = e).addNextTo(this.tail);
        this.tail = e;
        this.size += headerSize;
        this.ensureFreeCapacity();
        return e.index;
    }
    
    void acknowledgeInsertCountOnAck(final int entryIndex) throws QpackException {
        this.acknowledgeInsertCount(entryIndex, true);
    }
    
    void acknowledgeInsertCountOnCancellation(final int entryIndex) throws QpackException {
        this.acknowledgeInsertCount(entryIndex, false);
    }
    
    private void acknowledgeInsertCount(final int entryIndex, final boolean updateKnownReceived) throws QpackException {
        if (entryIndex < 0) {
            throw QpackEncoderDynamicTable.INVALID_REQUIRED_INSERT_COUNT_INCREMENT;
        }
        HeaderEntry e = this.head.next;
        while (e != null) {
            if (e.index == entryIndex) {
                assert e.refCount > 0;
                final HeaderEntry headerEntry = e;
                --headerEntry.refCount;
                if (updateKnownReceived && e.index > this.knownReceived.index) {
                    this.knownReceived = e;
                }
                this.evictUnreferencedEntries();
                return;
            }
            else {
                e = e.next;
            }
        }
        throw QpackEncoderDynamicTable.INVALID_REQUIRED_INSERT_COUNT_INCREMENT;
    }
    
    void incrementKnownReceivedCount(int knownReceivedCountIncr) throws QpackException {
        if (knownReceivedCountIncr <= 0) {
            throw QpackEncoderDynamicTable.INVALID_KNOW_RECEIVED_COUNT_INCREMENT;
        }
        while (this.knownReceived.next != null && knownReceivedCountIncr > 0) {
            this.knownReceived = this.knownReceived.next;
            --knownReceivedCountIncr;
        }
        if (knownReceivedCountIncr == 0) {
            this.evictUnreferencedEntries();
            return;
        }
        throw QpackEncoderDynamicTable.INVALID_KNOW_RECEIVED_COUNT_INCREMENT;
    }
    
    int insertCount() {
        return this.tail.index + 1;
    }
    
    int encodedRequiredInsertCount(final int reqInsertCount) {
        return (reqInsertCount == 0) ? 0 : (reqInsertCount % Math.toIntExact(2L * QpackUtil.maxEntries(this.maxTableCapacity)) + 1);
    }
    
    int encodedKnownReceivedCount() {
        return this.encodedRequiredInsertCount(this.knownReceived.index + 1);
    }
    
    void maxTableCapacity(final long capacity) throws QpackException {
        validateCapacity(capacity);
        if (this.maxTableCapacity >= 0L) {
            throw QpackEncoderDynamicTable.CAPACITY_ALREADY_SET;
        }
        this.maxTableCapacity = capacity;
    }
    
    int relativeIndexForEncoderInstructions(final int entryIndex) {
        assert entryIndex >= 0;
        assert entryIndex <= this.tail.index;
        return this.tail.index - entryIndex;
    }
    
    int getEntryIndex(@Nullable final CharSequence name, @Nullable final CharSequence value) {
        if (this.tail != this.head && name != null && value != null) {
            final int h = AsciiString.hashCode(name);
            final int i = this.index(h);
            HeaderEntry firstNameMatch = null;
            HeaderEntry entry = null;
            for (HeaderEntry e = this.fields[i]; e != null; e = e.nextSibling) {
                if (e.hash == h && QpackUtil.equalsVariableTime(value, e.value)) {
                    if (QpackUtil.equalsVariableTime(name, e.name)) {
                        entry = e;
                        break;
                    }
                }
                else if (firstNameMatch == null && QpackUtil.equalsVariableTime(name, e.name)) {
                    firstNameMatch = e;
                }
            }
            if (entry != null) {
                return entry.index;
            }
            if (firstNameMatch != null) {
                return -firstNameMatch.index - 1;
            }
        }
        return Integer.MIN_VALUE;
    }
    
    int addReferenceToEntry(@Nullable final CharSequence name, @Nullable final CharSequence value, final int idx) {
        if (this.tail != this.head && name != null && value != null) {
            final int h = AsciiString.hashCode(name);
            final int i = this.index(h);
            for (HeaderEntry e = this.fields[i]; e != null; e = e.nextSibling) {
                if (e.hash == h && idx == e.index) {
                    final HeaderEntry headerEntry = e;
                    ++headerEntry.refCount;
                    return e.index + 1;
                }
            }
        }
        throw new IllegalArgumentException("Index " + idx + " not found");
    }
    
    boolean requiresDuplication(final int idx, final long size) {
        assert this.head != this.tail;
        return this.size + size <= this.maxTableCapacity && this.head != this.drain && idx >= this.head.next.index && idx <= this.drain.index;
    }
    
    private void evictUnreferencedEntries() {
        if (this.head == this.knownReceived || this.head == this.drain) {
            return;
        }
        while (this.head.next != null && this.head.next != this.knownReceived.next && this.head.next != this.drain.next) {
            if (!this.removeIfUnreferenced()) {
                return;
            }
        }
    }
    
    private boolean removeIfUnreferenced() {
        final HeaderEntry toRemove = this.head.next;
        if (toRemove.refCount != 0) {
            return false;
        }
        this.size -= toRemove.size();
        final int i = this.index(toRemove.hash);
        HeaderEntry e = this.fields[i];
        HeaderEntry prev = null;
        while (e != null && e != toRemove) {
            prev = e;
            e = e.nextSibling;
        }
        if (e == toRemove) {
            if (prev == null) {
                this.fields[i] = e.nextSibling;
            }
            else {
                prev.nextSibling = e.nextSibling;
            }
        }
        toRemove.remove(this.head);
        if (toRemove == this.tail) {
            this.resetIndicesToHead();
        }
        if (toRemove == this.drain) {
            this.drain = this.head;
        }
        if (toRemove == this.knownReceived) {
            this.knownReceived = this.head;
        }
        return true;
    }
    
    private void resetIndicesToHead() {
        this.tail = this.head;
        this.drain = this.head;
        this.knownReceived = this.head;
    }
    
    private void ensureFreeCapacity() {
        long maxDesiredSize;
        long cSize;
        HeaderEntry nDrain;
        for (maxDesiredSize = Math.max(32L, (100 - this.expectedFreeCapacityPercentage) * this.maxTableCapacity / 100L), cSize = this.size, nDrain = this.head; nDrain.next != null && cSize > maxDesiredSize; cSize -= nDrain.next.size(), nDrain = nDrain.next) {}
        if (cSize != this.size) {
            this.drain = nDrain;
            this.evictUnreferencedEntries();
        }
    }
    
    private int index(final int h) {
        return h & this.hashMask;
    }
    
    private static void validateCapacity(final long capacity) throws QpackException {
        if (capacity < 0L || capacity > 4294967295L) {
            throw QpackEncoderDynamicTable.INVALID_TABLE_CAPACITY;
        }
    }
    
    static {
        INVALID_KNOW_RECEIVED_COUNT_INCREMENT = QpackException.newStatic(QpackDecoder.class, "incrementKnownReceivedCount(...)", "QPACK - invalid known received count increment.");
        INVALID_REQUIRED_INSERT_COUNT_INCREMENT = QpackException.newStatic(QpackDecoder.class, "acknowledgeInsertCount(...)", "QPACK - invalid required insert count acknowledgment.");
        INVALID_TABLE_CAPACITY = QpackException.newStatic(QpackDecoder.class, "validateCapacity(...)", "QPACK - dynamic table capacity is invalid.");
        CAPACITY_ALREADY_SET = QpackException.newStatic(QpackDecoder.class, "maxTableCapacity(...)", "QPACK - dynamic table capacity is already set.");
    }
    
    private static final class HeaderEntry extends QpackHeaderField
    {
        HeaderEntry next;
        HeaderEntry nextSibling;
        int refCount;
        final int hash;
        final int index;
        
        HeaderEntry(final int hash, final CharSequence name, final CharSequence value, final int index, @Nullable final HeaderEntry nextSibling) {
            super(name, value);
            this.index = index;
            this.hash = hash;
            this.nextSibling = nextSibling;
        }
        
        void remove(final HeaderEntry prev) {
            assert prev != this;
            prev.next = this.next;
            this.next = null;
            this.nextSibling = null;
        }
        
        void addNextTo(final HeaderEntry prev) {
            assert prev != this;
            this.next = prev.next;
            prev.next = this;
        }
    }
}
