### ----------------------------------------------------
### -- HTTP::HPACK
### -- Licenses: Artistic-2.0
### -- Authors: Jonathan Worthington
### -- File: lib/HTTP/HPACK.rakumod
### ----------------------------------------------------
### -- Chunk 2 of 2
 my int $idx = 0;
        while $idx < $packed.elems {
            my uint $header-start = $packed[$idx];
            if $header-start +& 128 {
                @headers.push(HTTP::HPACK::Header.new(
                    self!resolve-decoded-index(decode-int($packed, 7, $idx))
                ));
            }
            elsif $header-start +& 64 {
                my $header = self!decode-literal-header-field($packed, 6, $idx);
                @headers.push(HTTP::HPACK::Header.new($header));
                self!add-to-dynamic-table($header);
            }
            elsif $header-start +& 32 {
                given decode-int($packed, 5, $idx) {
                    my $old = $!dynamic-table-limit;
                    $!dynamic-table-limit = $_;
                    unless $old <= $!dynamic-table-limit {
                        while self.dynamic-table-size > $!dynamic-table-limit {
                            @!dynamic-table.pop;
                        }
                    }
                }
            }
            elsif $header-start +& 16 {
                given self!decode-literal-header-field($packed, 4, $idx) {
                    @headers.push(HTTP::HPACK::Header.new(
                        name => .key,
                        value => .value,
                        indexing => HTTP::HPACK::Indexing::NeverIndexed
                    ));
                }
            }
            else {
                given self!decode-literal-header-field($packed, 4, $idx) {
                    @headers.push(HTTP::HPACK::Header.new(
                        name => .key,
                        value => .value,
                        indexing => HTTP::HPACK::Indexing::NotIndexed
                    ));
                }
            }
        }
        @headers
    }

    method !decode-literal-header-field(Blob $packed, $prefix, int $idx is rw) returns Pair {
        my int $header-index = decode-int($packed, $prefix, $idx);
        if $header-index {
            self!resolve-decoded-index($header-index).key => decode-str($packed, $idx)
        }
        else {
            my ($key, $value) = decode-str($packed, $idx) xx 2;
            $key => $value
        }
    }


    sub decode-str(Blob $packed, int $blob-offset is rw) returns Str is export(:internal) {
        my uint $huffman = $packed[$blob-offset] +& 128;
        my uint $bytes = decode-int($packed, 7, $blob-offset);
        my $result-buf;
        if $huffman {
            $result-buf = Buf.new;
            my int $tree-pos = 0;
            my int $end = $blob-offset + $bytes;
            DECODE: while $blob-offset < $end {
                my uint $decode-byte = $packed[$blob-offset++];
                my uint $bit = 128;
                while $bit {
                    my int $node = HUFFMAN_TREE[$tree-pos + ($decode-byte +& $bit ?? 1 !! 0)];
                    if $node > 0 {
                        $tree-pos = $node;
                    }
                    elsif $node < 0 {
                        last DECODE if $node == -256;
                        $result-buf.push(-$node);
                        $tree-pos = 0;
                    }
                    else {
                        die "Invalid huffman code";
                    }
                    $bit +>= 1;
                }
            }
        }
        else {
            die X::HTTP::HPACK::Overflow.new(:what<string>, offset => $blob-offset)
                if $blob-offset + $bytes > $packed.bytes;
            $result-buf = $packed.subbuf($blob-offset, $bytes);
            $blob-offset += $bytes;
        }
        $result-buf.decode('latin-1')
    }
}

class HTTP::HPACK::Encoder does HTTP::HPACK::Tables {
    has Bool $.huffman = False;

    # Implements constraint: `@headers where all(@headers) ~~ HTTP::HPACK::Header` but in a way compatible with the windows all regression.
    method encode-headers(@headers where [and] @headers.map(* ~~ HTTP::HPACK::Header)) returns Blob {
        my $result = Buf.new;
        for @headers -> $header {
            # Search tables for a matching entry.
            my $match = self!find-table-entry($header);

            # If exact match and we're allowed to store indexed, emit indexed header
            # field representation.
            if $match && $match.value && $header.indexing == HTTP::HPACK::Indexing::Indexed {
                encode-int($match.key, 7, $result, 0b10000000);
            }

            # Literal Header Field with Incremental Indexing
            else {
                my $index = $match ?? $match.key !! 0;
                given $header.indexing {
                    when HTTP::HPACK::Indexing::Indexed {
                        encode-int($index, 6, $result, 0b01000000);
                        self!add-to-dynamic-table($header.name => $header.value);
                    }
                    when HTTP::HPACK::Indexing::NotIndexed {
                        encode-int($index, 4, $result);
                    }
                    when HTTP::HPACK::Indexing::NeverIndexed {
                        encode-int($index, 4, $result, 0b00010000);
                    }
                }
                self!encode-str($header.name, $result) unless $match;
                self!encode-str($header.value, $result);
            }
        }
        $result
    }

    method !encode-str(str $value, $target) {
        my $encoded = $value.encode('latin-1');
        if $!huffman {
            my $huffed = Buf.new;
            my uint $cur-byte = 0;
            my int $cur-bit = 8;
            for $encoded.list {
                my int $code = HUFFMAN_CODES[$_];
                my int $code-bit = HUFFMAN_LENGTHS[$_];
                while $code-bit-- {
                    my int $value = $code +& (1 +< $code-bit) ?? 1 !! 0;
                    $cur-byte +|= $value +< --$cur-bit;
                    if $cur-bit == 0 {
                        $huffed.push($cur-byte);
                        $cur-bit = 8;
                        $cur-byte = 0;
                    }
                }
            }
            if $cur-bit < 8 {
                while $cur-bit-- {
                    $cur-byte +|= 1 +< $cur-bit;
                }
                $huffed.push($cur-byte);
            }
            encode-int($huffed.bytes, 7, $target, 0b10000000);
            $target.append($huffed);
        }
        else {
            encode-int($encoded.bytes, 7, $target);
            $target.append($encoded);
        }
    }
}

sub encode-int(uint $value, int $prefix, $target = Buf.new, int $upper = 0)
        returns Buf is export(:internal) {
    my int $limit = 2 ** $prefix - 1;
    if $value < $limit {
        $target.push($value +| $upper);
    }
    else {
        $target.push($limit +| $upper);
        my uint $cur-value = $value - $limit;
        while $cur-value > 128 {
            $target.push($cur-value mod 128 + 128);
            $cur-value = $cur-value div 128;
        }
        $target.push($cur-value);
    }
    $target
}

sub decode-int(Blob $blob, int $prefix, int $blob-offset is rw) returns uint is export(:internal) {
    my int $limit = 2 ** $prefix - 1;
    my uint $result = $blob[$blob-offset] +& $limit;
    if $result >= $limit {
        my int $m = 0;
        my uint $cur-byte = $result;
        repeat while $cur-byte +& 128 {
            $cur-byte = $blob[++$blob-offset];
            $result += ($cur-byte +& 127) * 2 ** $m;
            $m += 7;
        }
    }
    $blob-offset++;
    $result
}

# vim: expandtab shiftwidth=4


