const std = @import("std");
const assert = std.debug.assert;
const math = std.math;
const mem = std.mem;
const Allocator = std.mem.Allocator;
const ArrayList = std.ArrayList;
const bu = @import("bits_utils.zig");
const ddec = @import("dict_decoder.zig");
const deflate_const = @import("deflate_const.zig");
const mu = @import("mem_utils.zig");
const max_match_offset = deflate_const.max_match_offset;
const end_block_marker = deflate_const.end_block_marker;
const max_code_len = 16; 
const max_num_lit = 286;
const max_num_dist = 30;
const num_codes = 19; 
var corrupt_input_error_offset: u64 = undefined;
const InflateError = error{
    CorruptInput, 
    BadInternalState, 
    BadReaderState, 
    UnexpectedEndOfStream,
    EndOfStreamWithNoError,
};
const huffman_chunk_bits = 9;
const huffman_num_chunks = 1 << huffman_chunk_bits; 
const huffman_count_mask = 15; 
const huffman_value_shift = 4;
const HuffmanDecoder = struct {
    const Self = @This();
    allocator: Allocator = undefined,
    min: u32 = 0, 
    chunks: [huffman_num_chunks]u16 = [1]u16{0} ** huffman_num_chunks, 
    links: [][]u16 = undefined, 
    link_mask: u32 = 0, 
    initialized: bool = false,
    sub_chunks: ArrayList(u32) = undefined,
    
    
    
    
    
    fn init(self: *Self, allocator: Allocator, lengths: []u32) !bool {
        
        
        
        const sanity = false;
        if (self.min != 0) {
            self.* = HuffmanDecoder{};
        }
        self.allocator = allocator;
        
        
        var count: [max_code_len]u32 = [1]u32{0} ** max_code_len;
        var min: u32 = 0;
        var max: u32 = 0;
        for (lengths) |n| {
            if (n == 0) {
                continue;
            }
            if (min == 0) {
                min = n;
            }
            min = @min(n, min);
            max = @max(n, max);
            count[n] += 1;
        }
        
        
        
        
        
        
        
        if (max == 0) {
            return true;
        }
        var next_code: [max_code_len]u32 = [1]u32{0} ** max_code_len;
        var code: u32 = 0;
        {
            var i = min;
            while (i <= max) : (i += 1) {
                code <<= 1;
                next_code[i] = code;
                code += count[i];
            }
        }
        
        
        
        
        
        if (code != @as(u32, 1) << @intCast(u5, max) and !(code == 1 and max == 1)) {
            return false;
        }
        self.min = min;
        if (max > huffman_chunk_bits) {
            var num_links = @as(u32, 1) << @intCast(u5, max - huffman_chunk_bits);
            self.link_mask = @intCast(u32, num_links - 1);
            
            var link = next_code[huffman_chunk_bits + 1] >> 1;
            self.links = try self.allocator.alloc([]u16, huffman_num_chunks - link);
            self.sub_chunks = ArrayList(u32).init(self.allocator);
            self.initialized = true;
            var j = @intCast(u32, link);
            while (j < huffman_num_chunks) : (j += 1) {
                var reverse = @intCast(u32, bu.bitReverse(u16, @intCast(u16, j), 16));
                reverse >>= @intCast(u32, 16 - huffman_chunk_bits);
                var off = j - @intCast(u32, link);
                if (sanity) {
                    
                    assert(self.chunks[reverse] == 0);
                }
                self.chunks[reverse] = @intCast(u16, off << huffman_value_shift | (huffman_chunk_bits + 1));
                self.links[off] = try self.allocator.alloc(u16, num_links);
                if (sanity) {
                    
                    
                    mem.set(u16, self.links[off], 0);
                }
                try self.sub_chunks.append(off);
            }
        }
        for (lengths) |n, li| {
            if (n == 0) {
                continue;
            }
            var ncode = next_code[n];
            next_code[n] += 1;
            var chunk = @intCast(u16, (li << huffman_value_shift) | n);
            var reverse = @intCast(u16, bu.bitReverse(u16, @intCast(u16, ncode), 16));
            reverse >>= @intCast(u4, 16 - n);
            if (n <= huffman_chunk_bits) {
                var off = reverse;
                while (off < self.chunks.len) : (off += @as(u16, 1) << @intCast(u4, n)) {
                    
                    
                    
                    
                    
                    if (sanity) {
                        assert(self.chunks[off] == 0);
                    }
                    self.chunks[off] = chunk;
                }
            } else {
                var j = reverse & (huffman_num_chunks - 1);
                if (sanity) {
                    
                    assert(self.chunks[j] & huffman_count_mask == huffman_chunk_bits + 1);
                    
                    
                }
                var value = self.chunks[j] >> huffman_value_shift;
                var link_tab = self.links[value];
                reverse >>= huffman_chunk_bits;
                var off = reverse;
                while (off < link_tab.len) : (off += @as(u16, 1) << @intCast(u4, n - huffman_chunk_bits)) {
                    if (sanity) {
                        
                        assert(link_tab[off] == 0);
                    }
                    link_tab[off] = @intCast(u16, chunk);
                }
            }
        }
        if (sanity) {
            
            
            
            for (self.chunks) |chunk, i| {
                
                
                
                if (code == 1 and i % 2 == 1) {
                    continue;
                }
                
                
                
                assert(chunk != 0);
            }
            if (self.initialized) {
                for (self.links) |link_tab| {
                    for (link_tab) |chunk| {
                        
                        assert(chunk != 0);
                    }
                }
            }
        }
        return true;
    }
    
    pub fn deinit(self: *Self) void {
        if (self.initialized and self.links.len > 0) {
            for (self.sub_chunks.items) |off| {
                self.allocator.free(self.links[off]);
            }
            self.allocator.free(self.links);
            self.sub_chunks.deinit();
            self.initialized = false;
        }
    }
};
var fixed_huffman_decoder: ?HuffmanDecoder = null;
fn fixedHuffmanDecoderInit(allocator: Allocator) !HuffmanDecoder {
    if (fixed_huffman_decoder != null) {
        return fixed_huffman_decoder.?;
    }
    
    var bits: [288]u32 = undefined;
    var i: u32 = 0;
    while (i < 144) : (i += 1) {
        bits[i] = 8;
    }
    while (i < 256) : (i += 1) {
        bits[i] = 9;
    }
    while (i < 280) : (i += 1) {
        bits[i] = 7;
    }
    while (i < 288) : (i += 1) {
        bits[i] = 8;
    }
    fixed_huffman_decoder = HuffmanDecoder{};
    _ = try fixed_huffman_decoder.?.init(allocator, &bits);
    return fixed_huffman_decoder.?;
}
const DecompressorState = enum {
    init,
    dict,
};
pub fn decompressor(allocator: Allocator, reader: anytype, dictionary: ?[]const u8) !Decompressor(@TypeOf(reader)) {
    return Decompressor(@TypeOf(reader)).init(allocator, reader, dictionary);
}
pub fn Decompressor(comptime ReaderType: type) type {
    return struct {
        const Self = @This();
        pub const Error =
            ReaderType.Error ||
            error{EndOfStream} ||
            InflateError ||
            Allocator.Error;
        pub const Reader = io.Reader(*Self, Error, read);
        allocator: Allocator,
        
        inner_reader: ReaderType,
        roffset: u64,
        
        b: u32,
        nb: u32,
        
        hd1: HuffmanDecoder,
        hd2: HuffmanDecoder,
        
        bits: *[max_num_lit + max_num_dist]u32,
        codebits: *[num_codes]u32,
        
        dict: ddec.DictDecoder,
        
        buf: [4]u8,
        
        
        step: std.meta.FnPtr(fn (*Self) Error!void),
        step_state: DecompressorState,
        final: bool,
        err: ?Error,
        to_read: []u8,
        
        hl: ?*HuffmanDecoder,
        
        hd: ?*HuffmanDecoder,
        copy_len: u32,
        copy_dist: u32,
        
        
        pub fn reader(self: *Self) Reader {
            return .{ .context = self };
        }
        fn init(allocator: Allocator, in_reader: ReaderType, dict: ?[]const u8) !Self {
            fixed_huffman_decoder = try fixedHuffmanDecoderInit(allocator);
            var bits = try allocator.create([max_num_lit + max_num_dist]u32);
            var codebits = try allocator.create([num_codes]u32);
            var dd = ddec.DictDecoder{};
            try dd.init(allocator, max_match_offset, dict);
            return Self{
                .allocator = allocator,
                
                .inner_reader = in_reader,
                .roffset = 0,
                
                .b = 0,
                .nb = 0,
                
                .hd1 = HuffmanDecoder{},
                .hd2 = HuffmanDecoder{},
                
                .bits = bits,
                .codebits = codebits,
                
                .dict = dd,
                
                .buf = [_]u8{0} ** 4,
                
                .step = nextBlock,
                .step_state = .init,
                .final = false,
                .err = null,
                .to_read = &[0]u8{},
                .hl = null,
                .hd = null,
                .copy_len = 0,
                .copy_dist = 0,
            };
        }
        
        pub fn deinit(self: *Self) void {
            self.hd2.deinit();
            self.hd1.deinit();
            self.dict.deinit();
            self.allocator.destroy(self.codebits);
            self.allocator.destroy(self.bits);
        }
        fn nextBlock(self: *Self) Error!void {
            while (self.nb < 1 + 2) {
                self.moreBits() catch |e| {
                    self.err = e;
                    return e;
                };
            }
            self.final = self.b & 1 == 1;
            self.b >>= 1;
            var typ = self.b & 3;
            self.b >>= 2;
            self.nb -= 1 + 2;
            switch (typ) {
                0 => try self.dataBlock(),
                1 => {
                    
                    self.hl = &fixed_huffman_decoder.?;
                    self.hd = null;
                    try self.huffmanBlock();
                },
                2 => {
                    
                    self.hd2.deinit();
                    self.hd1.deinit();
                    try self.readHuffman();
                    self.hl = &self.hd1;
                    self.hd = &self.hd2;
                    try self.huffmanBlock();
                },
                else => {
                    
                    corrupt_input_error_offset = self.roffset;
                    self.err = InflateError.CorruptInput;
                    return InflateError.CorruptInput;
                },
            }
        }
        
        
        pub fn read(self: *Self, output: []u8) Error!usize {
            while (true) {
                if (self.to_read.len > 0) {
                    var n = mu.copy(output, self.to_read);
                    self.to_read = self.to_read[n..];
                    if (self.to_read.len == 0 and
                        self.err != null)
                    {
                        if (self.err.? == InflateError.EndOfStreamWithNoError) {
                            return n;
                        }
                        return self.err.?;
                    }
                    return n;
                }
                if (self.err != null) {
                    if (self.err.? == InflateError.EndOfStreamWithNoError) {
                        return 0;
                    }
                    return self.err.?;
                }
                self.step(self) catch |e| {
                    self.err = e;
                    if (self.to_read.len == 0) {
                        self.to_read = self.dict.readFlush(); 
                    }
                };
            }
        }
        pub fn close(self: *Self) ?Error {
            if (@import("builtin").zig_backend == .stage1) {
                if (self.err == Error.EndOfStreamWithNoError) {
                    return null;
                }
                return self.err;
            }
            if (self.err == @as(?Error, error.EndOfStreamWithNoError)) {
                return null;
            }
            return self.err;
        }
        
        
        const code_order = [_]u32{ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 };
        fn readHuffman(self: *Self) Error!void {
            
            while (self.nb < 5 + 5 + 4) {
                try self.moreBits();
            }
            var nlit = @intCast(u32, self.b & 0x1F) + 257;
            if (nlit > max_num_lit) {
                corrupt_input_error_offset = self.roffset;
                self.err = InflateError.CorruptInput;
                return InflateError.CorruptInput;
            }
            self.b >>= 5;
            var ndist = @intCast(u32, self.b & 0x1F) + 1;
            if (ndist > max_num_dist) {
                corrupt_input_error_offset = self.roffset;
                self.err = InflateError.CorruptInput;
                return InflateError.CorruptInput;
            }
            self.b >>= 5;
            var nclen = @intCast(u32, self.b & 0xF) + 4;
            
            self.b >>= 4;
            self.nb -= 5 + 5 + 4;
            
            var i: u32 = 0;
            while (i < nclen) : (i += 1) {
                while (self.nb < 3) {
                    try self.moreBits();
                }
                self.codebits[code_order[i]] = @intCast(u32, self.b & 0x7);
                self.b >>= 3;
                self.nb -= 3;
            }
            i = nclen;
            while (i < code_order.len) : (i += 1) {
                self.codebits[code_order[i]] = 0;
            }
            if (!try self.hd1.init(self.allocator, self.codebits[0..])) {
                corrupt_input_error_offset = self.roffset;
                self.err = InflateError.CorruptInput;
                return InflateError.CorruptInput;
            }
            
            
            i = 0;
            var n = nlit + ndist;
            while (i < n) {
                var x = try self.huffSym(&self.hd1);
                if (x < 16) {
                    
                    self.bits[i] = x;
                    i += 1;
                    continue;
                }
                
                var rep: u32 = 0;
                var nb: u32 = 0;
                var b: u32 = 0;
                switch (x) {
                    16 => {
                        rep = 3;
                        nb = 2;
                        if (i == 0) {
                            corrupt_input_error_offset = self.roffset;
                            self.err = InflateError.CorruptInput;
                            return InflateError.CorruptInput;
                        }
                        b = self.bits[i - 1];
                    },
                    17 => {
                        rep = 3;
                        nb = 3;
                        b = 0;
                    },
                    18 => {
                        rep = 11;
                        nb = 7;
                        b = 0;
                    },
                    else => return error.BadInternalState, 
                }
                while (self.nb < nb) {
                    try self.moreBits();
                }
                rep += @intCast(u32, self.b & (@as(u32, 1) << @intCast(u5, nb)) - 1);
                self.b >>= @intCast(u5, nb);
                self.nb -= nb;
                if (i + rep > n) {
                    corrupt_input_error_offset = self.roffset;
                    self.err = InflateError.CorruptInput;
                    return InflateError.CorruptInput;
                }
                var j: u32 = 0;
                while (j < rep) : (j += 1) {
                    self.bits[i] = b;
                    i += 1;
                }
            }
            if (!try self.hd1.init(self.allocator, self.bits[0..nlit]) or
                !try self.hd2.init(self.allocator, self.bits[nlit .. nlit + ndist]))
            {
                corrupt_input_error_offset = self.roffset;
                self.err = InflateError.CorruptInput;
                return InflateError.CorruptInput;
            }
            
            
            
            
            if (self.hd1.min < self.bits[end_block_marker]) {
                self.hd1.min = self.bits[end_block_marker];
            }
            return;
        }
        
        
        
        
        fn huffmanBlock(self: *Self) Error!void {
            while (true) {
                switch (self.step_state) {
                    .init => {
                        
                        var v = try self.huffSym(self.hl.?);
                        var n: u32 = 0; 
                        var length: u32 = 0;
                        switch (v) {
                            0...255 => {
                                self.dict.writeByte(@intCast(u8, v));
                                if (self.dict.availWrite() == 0) {
                                    self.to_read = self.dict.readFlush();
                                    self.step = huffmanBlock;
                                    self.step_state = .init;
                                    return;
                                }
                                self.step_state = .init;
                                continue;
                            },
                            256 => {
                                self.finishBlock();
                                return;
                            },
                            
                            257...264 => {
                                length = v - (257 - 3);
                                n = 0;
                            },
                            265...268 => {
                                length = v * 2 - (265 * 2 - 11);
                                n = 1;
                            },
                            269...272 => {
                                length = v * 4 - (269 * 4 - 19);
                                n = 2;
                            },
                            273...276 => {
                                length = v * 8 - (273 * 8 - 35);
                                n = 3;
                            },
                            277...280 => {
                                length = v * 16 - (277 * 16 - 67);
                                n = 4;
                            },
                            281...284 => {
                                length = v * 32 - (281 * 32 - 131);
                                n = 5;
                            },
                            max_num_lit - 1 => { 
                                length = 258;
                                n = 0;
                            },
                            else => {
                                corrupt_input_error_offset = self.roffset;
                                self.err = InflateError.CorruptInput;
                                return InflateError.CorruptInput;
                            },
                        }
                        if (n > 0) {
                            while (self.nb < n) {
                                try self.moreBits();
                            }
                            length += @intCast(u32, self.b) & ((@as(u32, 1) << @intCast(u5, n)) - 1);
                            self.b >>= @intCast(u5, n);
                            self.nb -= n;
                        }
                        var dist: u32 = 0;
                        if (self.hd == null) {
                            while (self.nb < 5) {
                                try self.moreBits();
                            }
                            dist = @intCast(
                                u32,
                                bu.bitReverse(u8, @intCast(u8, (self.b & 0x1F) << 3), 8),
                            );
                            self.b >>= 5;
                            self.nb -= 5;
                        } else {
                            dist = try self.huffSym(self.hd.?);
                        }
                        switch (dist) {
                            0...3 => dist += 1,
                            4...max_num_dist - 1 => { 
                                var nb = @intCast(u32, dist - 2) >> 1;
                                
                                var extra = (dist & 1) << @intCast(u5, nb);
                                while (self.nb < nb) {
                                    try self.moreBits();
                                }
                                extra |= @intCast(u32, self.b & (@as(u32, 1) << @intCast(u5, nb)) - 1);
                                self.b >>= @intCast(u5, nb);
                                self.nb -= nb;
                                dist = (@as(u32, 1) << @intCast(u5, nb + 1)) + 1 + extra;
                            },
                            else => {
                                corrupt_input_error_offset = self.roffset;
                                self.err = InflateError.CorruptInput;
                                return InflateError.CorruptInput;
                            },
                        }
                        
                        if (dist > self.dict.histSize()) {
                            corrupt_input_error_offset = self.roffset;
                            self.err = InflateError.CorruptInput;
                            return InflateError.CorruptInput;
                        }
                        self.copy_len = length;
                        self.copy_dist = dist;
                        self.step_state = .dict;
                    },
                    .dict => {
                        
                        var cnt = self.dict.tryWriteCopy(self.copy_dist, self.copy_len);
                        if (cnt == 0) {
                            cnt = self.dict.writeCopy(self.copy_dist, self.copy_len);
                        }
                        self.copy_len -= cnt;
                        if (self.dict.availWrite() == 0 or self.copy_len > 0) {
                            self.to_read = self.dict.readFlush();
                            self.step = huffmanBlock; 
                            self.step_state = .dict;
                            return;
                        }
                        self.step_state = .init;
                    },
                }
            }
        }
        
        fn dataBlock(self: *Self) Error!void {
            
            
            self.nb = 0;
            self.b = 0;
            
            var nr: u32 = 4;
            self.inner_reader.readNoEof(self.buf[0..nr]) catch {
                self.err = InflateError.UnexpectedEndOfStream;
                return InflateError.UnexpectedEndOfStream;
            };
            self.roffset += @intCast(u64, nr);
            var n = @intCast(u32, self.buf[0]) | @intCast(u32, self.buf[1]) << 8;
            var nn = @intCast(u32, self.buf[2]) | @intCast(u32, self.buf[3]) << 8;
            if (@intCast(u16, nn) != @truncate(u16, ~n)) {
                corrupt_input_error_offset = self.roffset;
                self.err = InflateError.CorruptInput;
                return InflateError.CorruptInput;
            }
            if (n == 0) {
                self.to_read = self.dict.readFlush();
                self.finishBlock();
                return;
            }
            self.copy_len = n;
            try self.copyData();
        }
        
        
        fn copyData(self: *Self) Error!void {
            var buf = self.dict.writeSlice();
            if (buf.len > self.copy_len) {
                buf = buf[0..self.copy_len];
            }
            var cnt = try self.inner_reader.read(buf);
            if (cnt < buf.len) {
                self.err = InflateError.UnexpectedEndOfStream;
            }
            self.roffset += @intCast(u64, cnt);
            self.copy_len -= @intCast(u32, cnt);
            self.dict.writeMark(@intCast(u32, cnt));
            if (self.err != null) {
                return InflateError.UnexpectedEndOfStream;
            }
            if (self.dict.availWrite() == 0 or self.copy_len > 0) {
                self.to_read = self.dict.readFlush();
                self.step = copyData;
                return;
            }
            self.finishBlock();
        }
        fn finishBlock(self: *Self) void {
            if (self.final) {
                if (self.dict.availRead() > 0) {
                    self.to_read = self.dict.readFlush();
                }
                self.err = InflateError.EndOfStreamWithNoError;
            }
            self.step = nextBlock;
        }
        fn moreBits(self: *Self) InflateError!void {
            var c = self.inner_reader.readByte() catch |e| {
                if (e == error.EndOfStream) {
                    return InflateError.UnexpectedEndOfStream;
                }
                return InflateError.BadReaderState;
            };
            self.roffset += 1;
            self.b |= @as(u32, c) << @intCast(u5, self.nb);
            self.nb += 8;
            return;
        }
        
        fn huffSym(self: *Self, h: *HuffmanDecoder) InflateError!u32 {
            
            
            
            
            var n: u32 = h.min;
            
            
            
            var nb = self.nb;
            var b = self.b;
            while (true) {
                while (nb < n) {
                    var c = self.inner_reader.readByte() catch |e| {
                        self.b = b;
                        self.nb = nb;
                        if (e == error.EndOfStream) {
                            return error.UnexpectedEndOfStream;
                        }
                        return InflateError.BadReaderState;
                    };
                    self.roffset += 1;
                    b |= @intCast(u32, c) << @intCast(u5, nb & 31);
                    nb += 8;
                }
                var chunk = h.chunks[b & (huffman_num_chunks - 1)];
                n = @intCast(u32, chunk & huffman_count_mask);
                if (n > huffman_chunk_bits) {
                    chunk = h.links[chunk >> huffman_value_shift][(b >> huffman_chunk_bits) & h.link_mask];
                    n = @intCast(u32, chunk & huffman_count_mask);
                }
                if (n <= nb) {
                    if (n == 0) {
                        self.b = b;
                        self.nb = nb;
                        corrupt_input_error_offset = self.roffset;
                        self.err = InflateError.CorruptInput;
                        return InflateError.CorruptInput;
                    }
                    self.b = b >> @intCast(u5, n & 31);
                    self.nb = nb - n;
                    return @intCast(u32, chunk >> huffman_value_shift);
                }
            }
        }
        
        
        pub fn reset(s: *Self, new_reader: ReaderType, new_dict: ?[]const u8) !void {
            s.inner_reader = new_reader;
            s.step = nextBlock;
            s.err = null;
            s.dict.deinit();
            try s.dict.init(s.allocator, max_match_offset, new_dict);
            return;
        }
    };
}
const expect = std.testing.expect;
const expectError = std.testing.expectError;
const io = std.io;
const testing = std.testing;
test "truncated input" {
    const TruncatedTest = struct {
        input: []const u8,
        output: []const u8,
    };
    const tests = [_]TruncatedTest{
        .{ .input = "\x00", .output = "" },
        .{ .input = "\x00\x0c", .output = "" },
        .{ .input = "\x00\x0c\x00", .output = "" },
        .{ .input = "\x00\x0c\x00\xf3\xff", .output = "" },
        .{ .input = "\x00\x0c\x00\xf3\xffhello", .output = "hello" },
        .{ .input = "\x00\x0c\x00\xf3\xffhello, world", .output = "hello, world" },
        .{ .input = "\x02", .output = "" },
        .{ .input = "\xf2H\xcd", .output = "He" },
        .{ .input = "\xf2H͙0a\u{0084}\t", .output = "Hel\x90\x90\x90\x90\x90" },
        .{ .input = "\xf2H͙0a\u{0084}\t\x00", .output = "Hel\x90\x90\x90\x90\x90" },
    };
    for (tests) |t| {
        var fib = io.fixedBufferStream(t.input);
        const r = fib.reader();
        var z = try decompressor(testing.allocator, r, null);
        defer z.deinit();
        var zr = z.reader();
        var output = [1]u8{0} ** 12;
        try expectError(error.UnexpectedEndOfStream, zr.readAll(&output));
        try expect(mem.eql(u8, output[0..t.output.len], t.output));
    }
}
test "Go non-regression test for 9842" {
    
    const Test = struct {
        err: ?anyerror,
        input: []const u8,
    };
    const tests = [_]Test{
        .{ .err = error.UnexpectedEndOfStream, .input = ("\x95\x90=o\xc20\x10\x86\xf30") },
        .{ .err = error.CorruptInput, .input = ("\x950\x00\x0000000") },
        
        
        .{ .err = error.CorruptInput, .input = ("\x950000") },
        .{ .err = error.CorruptInput, .input = ("\x05000") },
        
        .{ .err = error.CorruptInput, .input = ("\x05\xea\x01\t\x00\x00\x00\x01\x00\\\xbf.\t\x00") },
        
        .{ .err = error.CorruptInput, .input = ("\x05\xe0\x01A\x00\x00\x00\x00\x10\\\xbf.") },
        
        .{ .err = error.CorruptInput, .input = ("\x05\xe0\x01\t\x00\x00\x00\x00\x10\\\xbf\xce") },
        .{ .err = null, .input = "\x15\xe0\x01\t\x00\x00\x00\x00\x10\\\xbf.0" },
    };
    for (tests) |t| {
        var fib = std.io.fixedBufferStream(t.input);
        const reader = fib.reader();
        var decomp = try decompressor(testing.allocator, reader, null);
        defer decomp.deinit();
        var output: [10]u8 = undefined;
        if (t.err != null) {
            try expectError(t.err.?, decomp.reader().read(&output));
        } else {
            _ = try decomp.reader().read(&output);
        }
    }
}
test "inflate A Tale of Two Cities (1859) intro" {
    const compressed = [_]u8{
        0x74, 0xeb, 0xcd, 0x0d, 0x80, 0x20, 0x0c, 0x47, 0x71, 0xdc, 0x9d, 0xa2, 0x03, 0xb8, 0x88,
        0x63, 0xf0, 0xf1, 0x47, 0x9a, 0x00, 0x35, 0xb4, 0x86, 0xf5, 0x0d, 0x27, 0x63, 0x82, 0xe7,
        0xdf, 0x7b, 0x87, 0xd1, 0x70, 0x4a, 0x96, 0x41, 0x1e, 0x6a, 0x24, 0x89, 0x8c, 0x2b, 0x74,
        0xdf, 0xf8, 0x95, 0x21, 0xfd, 0x8f, 0xdc, 0x89, 0x09, 0x83, 0x35, 0x4a, 0x5d, 0x49, 0x12,
        0x29, 0xac, 0xb9, 0x41, 0xbf, 0x23, 0x2e, 0x09, 0x79, 0x06, 0x1e, 0x85, 0x91, 0xd6, 0xc6,
        0x2d, 0x74, 0xc4, 0xfb, 0xa1, 0x7b, 0x0f, 0x52, 0x20, 0x84, 0x61, 0x28, 0x0c, 0x63, 0xdf,
        0x53, 0xf4, 0x00, 0x1e, 0xc3, 0xa5, 0x97, 0x88, 0xf4, 0xd9, 0x04, 0xa5, 0x2d, 0x49, 0x54,
        0xbc, 0xfd, 0x90, 0xa5, 0x0c, 0xae, 0xbf, 0x3f, 0x84, 0x77, 0x88, 0x3f, 0xaf, 0xc0, 0x40,
        0xd6, 0x5b, 0x14, 0x8b, 0x54, 0xf6, 0x0f, 0x9b, 0x49, 0xf7, 0xbf, 0xbf, 0x36, 0x54, 0x5a,
        0x0d, 0xe6, 0x3e, 0xf0, 0x9e, 0x29, 0xcd, 0xa1, 0x41, 0x05, 0x36, 0x48, 0x74, 0x4a, 0xe9,
        0x46, 0x66, 0x2a, 0x19, 0x17, 0xf4, 0x71, 0x8e, 0xcb, 0x15, 0x5b, 0x57, 0xe4, 0xf3, 0xc7,
        0xe7, 0x1e, 0x9d, 0x50, 0x08, 0xc3, 0x50, 0x18, 0xc6, 0x2a, 0x19, 0xa0, 0xdd, 0xc3, 0x35,
        0x82, 0x3d, 0x6a, 0xb0, 0x34, 0x92, 0x16, 0x8b, 0xdb, 0x1b, 0xeb, 0x7d, 0xbc, 0xf8, 0x16,
        0xf8, 0xc2, 0xe1, 0xaf, 0x81, 0x7e, 0x58, 0xf4, 0x9f, 0x74, 0xf8, 0xcd, 0x39, 0xd3, 0xaa,
        0x0f, 0x26, 0x31, 0xcc, 0x8d, 0x9a, 0xd2, 0x04, 0x3e, 0x51, 0xbe, 0x7e, 0xbc, 0xc5, 0x27,
        0x3d, 0xa5, 0xf3, 0x15, 0x63, 0x94, 0x42, 0x75, 0x53, 0x6b, 0x61, 0xc8, 0x01, 0x13, 0x4d,
        0x23, 0xba, 0x2a, 0x2d, 0x6c, 0x94, 0x65, 0xc7, 0x4b, 0x86, 0x9b, 0x25, 0x3e, 0xba, 0x01,
        0x10, 0x84, 0x81, 0x28, 0x80, 0x55, 0x1c, 0xc0, 0xa5, 0xaa, 0x36, 0xa6, 0x09, 0xa8, 0xa1,
        0x85, 0xf9, 0x7d, 0x45, 0xbf, 0x80, 0xe4, 0xd1, 0xbb, 0xde, 0xb9, 0x5e, 0xf1, 0x23, 0x89,
        0x4b, 0x00, 0xd5, 0x59, 0x84, 0x85, 0xe3, 0xd4, 0xdc, 0xb2, 0x66, 0xe9, 0xc1, 0x44, 0x0b,
        0x1e, 0x84, 0xec, 0xe6, 0xa1, 0xc7, 0x42, 0x6a, 0x09, 0x6d, 0x9a, 0x5e, 0x70, 0xa2, 0x36,
        0x94, 0x29, 0x2c, 0x85, 0x3f, 0x24, 0x39, 0xf3, 0xae, 0xc3, 0xca, 0xca, 0xaf, 0x2f, 0xce,
        0x8e, 0x58, 0x91, 0x00, 0x25, 0xb5, 0xb3, 0xe9, 0xd4, 0xda, 0xef, 0xfa, 0x48, 0x7b, 0x3b,
        0xe2, 0x63, 0x12, 0x00, 0x00, 0x20, 0x04, 0x80, 0x70, 0x36, 0x8c, 0xbd, 0x04, 0x71, 0xff,
        0xf6, 0x0f, 0x66, 0x38, 0xcf, 0xa1, 0x39, 0x11, 0x0f,
    };
    const expected =
        \\It was the best of times,
        \\it was the worst of times,
        \\it was the age of wisdom,
        \\it was the age of foolishness,
        \\it was the epoch of belief,
        \\it was the epoch of incredulity,
        \\it was the season of Light,
        \\it was the season of Darkness,
        \\it was the spring of hope,
        \\it was the winter of despair,
        \\
        \\we had everything before us, we had nothing before us, we were all going direct to Heaven, we were all going direct the other way---in short, the period was so far like the present period, that some of its noisiest authorities insisted on its being received, for good or for evil, in the superlative degree of comparison only.
        \\
    ;
    var fib = std.io.fixedBufferStream(&compressed);
    const reader = fib.reader();
    var decomp = try decompressor(testing.allocator, reader, null);
    defer decomp.deinit();
    var got: [700]u8 = undefined;
    var got_len = try decomp.reader().read(&got);
    try expect(got_len == 616);
    try expect(mem.eql(u8, got[0..expected.len], expected));
}
test "lengths overflow" {
    
    
    
    const stream = [_]u8{
        0b11101101, 0b00011101, 0b00100100, 0b11101001, 0b11111111, 0b11111111, 0b00111001,
        0b00001110,
    };
    try expectError(error.CorruptInput, decompress(stream[0..]));
}
test "empty distance alphabet" {
    
    
    
    const stream = [_]u8{
        0b00000101, 0b11100000, 0b00000001, 0b00001001, 0b00000000, 0b00000000,
        0b00000000, 0b00000000, 0b00010000, 0b01011100, 0b10111111, 0b00101110,
    };
    try decompress(stream[0..]);
}
test "distance past beginning of output stream" {
    
    
    const stream = [_]u8{ 0b01110011, 0b01110100, 0b01110010, 0b00000110, 0b01100001, 0b00000000 };
    try std.testing.expectError(error.CorruptInput, decompress(stream[0..]));
}
test "fuzzing" {
    const compressed = [_]u8{
        0x0a, 0x08, 0x50, 0xeb, 0x25, 0x05, 0xfc, 0x30, 0x0b, 0x0a, 0x08, 0x50, 0xeb, 0x25, 0x05,
    } ++ [_]u8{0xe1} ** 15 ++ [_]u8{0x30} ++ [_]u8{0xe1} ** 1481;
    try expectError(error.UnexpectedEndOfStream, decompress(&compressed));
    
    try expectError(error.UnexpectedEndOfStream, decompress("\x95\x90=o\xc20\x10\x86\xf30"));
    try expectError(error.CorruptInput, decompress("\x950\x00\x0000000"));
    
    
    try expectError(error.CorruptInput, decompress("\x950000"));
    try expectError(error.CorruptInput, decompress("\x05000"));
    
    try expectError(error.CorruptInput, decompress("\x05\xea\x01\t\x00\x00\x00\x01\x00\\\xbf.\t\x00"));
    
    try expectError(error.CorruptInput, decompress("\x05\xe0\x01A\x00\x00\x00\x00\x10\\\xbf."));
    
    try expectError(error.CorruptInput, decompress("\x05\xe0\x01\t\x00\x00\x00\x00\x10\\\xbf\xce"));
    try decompress("\x15\xe0\x01\t\x00\x00\x00\x00\x10\\\xbf.0");
}
fn decompress(input: []const u8) !void {
    const allocator = testing.allocator;
    var fib = std.io.fixedBufferStream(input);
    const reader = fib.reader();
    var decomp = try decompressor(allocator, reader, null);
    defer decomp.deinit();
    var output = try decomp.reader().readAllAlloc(allocator, math.maxInt(usize));
    defer std.testing.allocator.free(output);
}