// Copyright 2019+ Klaus Post. All rights reserved.
// License information can be found in the LICENSE file.
// Based on work by Yann Collet, released under BSD License.

package zstd

import (
	"bufio"
	"bytes"
	"encoding/binary"
	"encoding/hex"
	"errors"
	"fmt"
	"io"
	"log"
	"math/rand"
	"os"
	"path/filepath"
	"reflect"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"testing"
	"time"

	// "github.com/DataDog/zstd"
	// zstd "github.com/valyala/gozstd"

	"github.com/klauspost/compress/zstd/internal/xxhash"
)

func TestNewReaderMismatch(t *testing.T) {
	// To identify a potential decoding error, do the following steps:
	// 1) Place the compressed file in testdata, eg 'testdata/backup.bin.zst'
	// 2) Decompress the file to using zstd, so it will be named 'testdata/backup.bin'
	// 3) Run the test. A hash file will be generated 'testdata/backup.bin.hash'
	// 4) The decoder will also run and decode the file. It will stop as soon as a mismatch is found.
	// The hash file will be reused between runs if present.
	const baseFile = "testdata/backup.bin"
	const blockSize = 1024
	hashes, err := os.ReadFile(baseFile + ".hash")
	if os.IsNotExist(err) {
		// Create the hash file.
		f, err := os.Open(baseFile)
		if os.IsNotExist(err) {
			t.Skip("no decompressed file found")
			return
		}
		defer f.Close()
		br := bufio.NewReader(f)
		var tmp [8]byte
		xx := xxhash.New()
		for {
			xx.Reset()
			buf := make([]byte, blockSize)
			n, err := io.ReadFull(br, buf)
			if err != nil {
				if err != io.EOF && err != io.ErrUnexpectedEOF {
					t.Fatal(err)
				}
			}
			if n > 0 {
				_, _ = xx.Write(buf[:n])
				binary.LittleEndian.PutUint64(tmp[:], xx.Sum64())
				hashes = append(hashes, tmp[4:]...)
			}
			if n != blockSize {
				break
			}
		}
		err = os.WriteFile(baseFile+".hash", hashes, os.ModePerm)
		if err != nil {
			// We can continue for now
			t.Error(err)
		}
		t.Log("Saved", len(hashes)/4, "hashes as", baseFile+".hash")
	}

	f, err := os.Open(baseFile + ".zst")
	if os.IsNotExist(err) {
		t.Skip("no compressed file found")
		return
	}
	defer f.Close()
	dec, err := NewReader(f, WithDecoderConcurrency(1))
	if err != nil {
		t.Fatal(err)
	}
	defer dec.Close()
	var tmp [8]byte
	xx := xxhash.New()
	var cHash int
	for {
		xx.Reset()
		buf := make([]byte, blockSize)
		n, err := io.ReadFull(dec, buf)
		if err != nil {
			if err != io.EOF && err != io.ErrUnexpectedEOF {
				t.Fatal("block", cHash, "err:", err)
			}
		}
		if n > 0 {
			if cHash+4 > len(hashes) {
				extra, _ := io.Copy(io.Discard, dec)
				t.Fatal("not enough hashes (length mismatch). Only have", len(hashes)/4, "hashes. Got block of", n, "bytes and", extra, "bytes still on stream.")
			}
			_, _ = xx.Write(buf[:n])
			binary.LittleEndian.PutUint64(tmp[:], xx.Sum64())
			want, got := hashes[cHash:cHash+4], tmp[4:]
			if !bytes.Equal(want, got) {
				org, err := os.Open(baseFile)
				if err == nil {
					const sizeBack = 8 << 20
					defer org.Close()
					start := int64(cHash)/4*blockSize - sizeBack
					if start < 0 {
						start = 0
					}
					_, err = org.Seek(start, io.SeekStart)
					if err != nil {
						t.Fatal(err)
					}
					buf2 := make([]byte, sizeBack+1<<20)
					n, _ := io.ReadFull(org, buf2)
					if n > 0 {
						err = os.WriteFile(baseFile+".section", buf2[:n], os.ModePerm)
						if err == nil {
							t.Log("Wrote problematic section to", baseFile+".section")
						}
					}
				}

				t.Fatal("block", cHash/4, "offset", cHash/4*blockSize, "hash mismatch, want:", hex.EncodeToString(want), "got:", hex.EncodeToString(got))
			}
			cHash += 4
		}
		if n != blockSize {
			break
		}
	}
	t.Log("Output matched")
}

type errorReader struct {
	err error
}

func (r *errorReader) Read(p []byte) (int, error) {
	return 0, r.err
}

func TestErrorReader(t *testing.T) {
	wantErr := fmt.Errorf("i'm a failure")
	zr, err := NewReader(&errorReader{err: wantErr})
	if err != nil {
		t.Fatal(err)
	}
	defer zr.Close()

	_, err = io.ReadAll(zr)
	if !errors.Is(err, wantErr) {
		t.Errorf("want error %v, got %v", wantErr, err)
	}
}

type failingWriter struct {
	err error
}

func (f failingWriter) Write(_ []byte) (n int, err error) {
	return 0, f.err
}

func TestErrorWriter(t *testing.T) {
	input := make([]byte, 100)
	cmp := bytes.Buffer{}
	w, err := NewWriter(&cmp)
	if err != nil {
		t.Fatal(err)
	}
	_, _ = rand.Read(input)
	_, err = w.Write(input)
	if err != nil {
		t.Fatal(err)
	}
	err = w.Close()
	if err != nil {
		t.Fatal(err)
	}
	wantErr := fmt.Errorf("i'm a failure")
	zr, err := NewReader(&cmp)
	if err != nil {
		t.Fatal(err)
	}
	defer zr.Close()
	out := failingWriter{err: wantErr}
	_, err = zr.WriteTo(out)
	if !errors.Is(err, wantErr) {
		t.Errorf("error: wanted: %v, got: %v", wantErr, err)
	}
}

func TestNewDecoder(t *testing.T) {
	for _, n := range []int{1, 4} {
		for _, ignoreCRC := range []bool{false, true} {
			t.Run(fmt.Sprintf("cpu-%d", n), func(t *testing.T) {
				newFn := func() (*Decoder, error) {
					return NewReader(nil, WithDecoderConcurrency(n), IgnoreChecksum(ignoreCRC))
				}
				testDecoderFile(t, "testdata/decoder.zip", newFn)
				dec, err := newFn()
				if err != nil {
					t.Fatal(err)
				}
				testDecoderDecodeAll(t, "testdata/decoder.zip", dec)
			})
		}
	}
}

func TestNewDecoderMemory(t *testing.T) {
	defer timeout(60 * time.Second)()
	var testdata bytes.Buffer
	enc, err := NewWriter(&testdata, WithWindowSize(32<<10), WithSingleSegment(false))
	if err != nil {
		t.Fatal(err)
	}
	// Write 256KB
	for i := 0; i < 256; i++ {
		tmp := strings.Repeat(string([]byte{byte(i)}), 1024)
		_, err := enc.Write([]byte(tmp))
		if err != nil {
			t.Fatal(err)
		}
	}
	err = enc.Close()
	if err != nil {
		t.Fatal(err)
	}

	var n = 5000
	if testing.Short() {
		n = 200
	}

	// 16K buffer
	var tmp [16 << 10]byte

	var before, after runtime.MemStats
	runtime.GC()
	runtime.ReadMemStats(&before)

	var decs = make([]*Decoder, n)
	for i := range decs {
		// Wrap in NopCloser to avoid shortcut.
		input := io.NopCloser(bytes.NewBuffer(testdata.Bytes()))
		decs[i], err = NewReader(input, WithDecoderConcurrency(1), WithDecoderLowmem(true))
		if err != nil {
			t.Fatal(err)
		}
	}

	for i := range decs {
		_, err := io.ReadFull(decs[i], tmp[:])
		if err != nil {
			t.Fatal(err)
		}
	}

	runtime.GC()
	runtime.ReadMemStats(&after)
	size := (after.HeapInuse - before.HeapInuse) / uint64(n) / 1024

	const expect = 124
	t.Log(size, "KiB per decoder")
	// This is not exact science, but fail if we suddenly get more than 2x what we expect.
	if size > expect*2 && !testing.Short() {
		t.Errorf("expected < %dKB per decoder, got %d", expect, size)
	}

	for _, dec := range decs {
		dec.Close()
	}
}

func TestNewDecoderMemoryHighMem(t *testing.T) {
	defer timeout(60 * time.Second)()
	var testdata bytes.Buffer
	enc, err := NewWriter(&testdata, WithWindowSize(32<<10), WithSingleSegment(false))
	if err != nil {
		t.Fatal(err)
	}
	// Write 256KB
	for i := 0; i < 256; i++ {
		tmp := strings.Repeat(string([]byte{byte(i)}), 1024)
		_, err := enc.Write([]byte(tmp))
		if err != nil {
			t.Fatal(err)
		}
	}
	err = enc.Close()
	if err != nil {
		t.Fatal(err)
	}

	var n = 50
	if testing.Short() {
		n = 10
	}

	// 16K buffer
	var tmp [16 << 10]byte

	var before, after runtime.MemStats
	runtime.GC()
	runtime.ReadMemStats(&before)

	var decs = make([]*Decoder, n)
	for i := range decs {
		// Wrap in NopCloser to avoid shortcut.
		input := io.NopCloser(bytes.NewBuffer(testdata.Bytes()))
		decs[i], err = NewReader(input, WithDecoderConcurrency(1), WithDecoderLowmem(false))
		if err != nil {
			t.Fatal(err)
		}
	}

	for i := range decs {
		_, err := io.ReadFull(decs[i], tmp[:])
		if err != nil {
			t.Fatal(err)
		}
	}

	runtime.GC()
	runtime.ReadMemStats(&after)
	size := (after.HeapInuse - before.HeapInuse) / uint64(n) / 1024

	const expect = 3915
	t.Log(size, "KiB per decoder")
	// This is not exact science, but fail if we suddenly get more than 2x what we expect.
	if size > expect*2 && !testing.Short() {
		t.Errorf("expected < %dKB per decoder, got %d", expect, size)
	}

	for _, dec := range decs {
		dec.Close()
	}
}

func TestNewDecoderFrameSize(t *testing.T) {
	defer timeout(60 * time.Second)()
	var testdata bytes.Buffer
	enc, err := NewWriter(&testdata, WithWindowSize(64<<10))
	if err != nil {
		t.Fatal(err)
	}
	// Write 256KB
	for i := 0; i < 256; i++ {
		tmp := strings.Repeat(string([]byte{byte(i)}), 1024)
		_, err := enc.Write([]byte(tmp))
		if err != nil {
			t.Fatal(err)
		}
	}
	err = enc.Close()
	if err != nil {
		t.Fatal(err)
	}
	// Must fail
	dec, err := NewReader(bytes.NewReader(testdata.Bytes()), WithDecoderMaxWindow(32<<10))
	if err != nil {
		t.Fatal(err)
	}
	_, err = io.Copy(io.Discard, dec)
	if err == nil {
		dec.Close()
		t.Fatal("Wanted error, got none")
	}
	dec.Close()

	// Must succeed.
	dec, err = NewReader(bytes.NewReader(testdata.Bytes()), WithDecoderMaxWindow(64<<10))
	if err != nil {
		t.Fatal(err)
	}
	_, err = io.Copy(io.Discard, dec)
	if err != nil {
		dec.Close()
		t.Fatalf("Wanted no error, got %+v", err)
	}
	dec.Close()
}

func TestNewDecoderGood(t *testing.T) {
	for _, n := range []int{1, 4} {
		t.Run(fmt.Sprintf("cpu-%d", n), func(t *testing.T) {
			newFn := func() (*Decoder, error) {
				return NewReader(nil, WithDecoderConcurrency(n))
			}
			testDecoderFile(t, "testdata/good.zip", newFn)
			dec, err := newFn()
			if err != nil {
				t.Fatal(err)
			}
			testDecoderDecodeAll(t, "testdata/good.zip", dec)
		})
	}
}

func TestNewDecoderBad(t *testing.T) {
	var errMap = make(map[string]string)
	if true {
		t.Run("Reader-4", func(t *testing.T) {
			newFn := func() (*Decoder, error) {
				return NewReader(nil, WithDecoderConcurrency(4), WithDecoderMaxMemory(1<<30))
			}
			testDecoderFileBad(t, "testdata/bad.zip", newFn, errMap)

		})
		t.Run("Reader-1", func(t *testing.T) {
			newFn := func() (*Decoder, error) {
				return NewReader(nil, WithDecoderConcurrency(1), WithDecoderMaxMemory(1<<30))
			}
			testDecoderFileBad(t, "testdata/bad.zip", newFn, errMap)
		})
		t.Run("Reader-4-bigmem", func(t *testing.T) {
			newFn := func() (*Decoder, error) {
				return NewReader(nil, WithDecoderConcurrency(4), WithDecoderMaxMemory(1<<30), WithDecoderLowmem(false))
			}
			testDecoderFileBad(t, "testdata/bad.zip", newFn, errMap)

		})
		t.Run("Reader-1-bigmem", func(t *testing.T) {
			newFn := func() (*Decoder, error) {
				return NewReader(nil, WithDecoderConcurrency(1), WithDecoderMaxMemory(1<<30), WithDecoderLowmem(false))
			}
			testDecoderFileBad(t, "testdata/bad.zip", newFn, errMap)
		})
	}
	t.Run("DecodeAll", func(t *testing.T) {
		defer timeout(10 * time.Second)()
		dec, err := NewReader(nil, WithDecoderMaxMemory(1<<30))
		if err != nil {
			t.Fatal(err)
		}
		testDecoderDecodeAllError(t, "testdata/bad.zip", dec, errMap)
	})
	t.Run("DecodeAll-bigmem", func(t *testing.T) {
		defer timeout(10 * time.Second)()
		dec, err := NewReader(nil, WithDecoderMaxMemory(1<<30), WithDecoderLowmem(false))
		if err != nil {
			t.Fatal(err)
		}
		testDecoderDecodeAllError(t, "testdata/bad.zip", dec, errMap)
	})
}

func TestNewDecoderLarge(t *testing.T) {
	newFn := func() (*Decoder, error) {
		return NewReader(nil)
	}
	testDecoderFile(t, "testdata/large.zip", newFn)
	dec, err := NewReader(nil)
	if err != nil {
		t.Fatal(err)
	}
	testDecoderDecodeAll(t, "testdata/large.zip", dec)
}

func TestNewReaderRead(t *testing.T) {
	dec, err := NewReader(nil)
	if err != nil {
		t.Fatal(err)
	}
	defer dec.Close()
	_, err = dec.Read([]byte{0})
	if err == nil {
		t.Fatal("Wanted error on uninitialized read, got nil")
	}
	t.Log("correctly got error", err)
}

func TestNewDecoderBig(t *testing.T) {
	if testing.Short() || isRaceTest {
		t.SkipNow()
	}
	file := "testdata/zstd-10kfiles.zip"
	if _, err := os.Stat(file); os.IsNotExist(err) {
		t.Skip("To run extended tests, download https://files.klauspost.com/compress/zstd-10kfiles.zip \n" +
			"and place it in " + file + "\n" + "Running it requires about 5GB of RAM")
	}
	newFn := func() (*Decoder, error) {
		return NewReader(nil)
	}
	testDecoderFile(t, file, newFn)
	dec, err := NewReader(nil)
	if err != nil {
		t.Fatal(err)
	}
	testDecoderDecodeAll(t, file, dec)
}

func TestNewDecoderBigFile(t *testing.T) {
	if testing.Short() || isRaceTest {
		t.SkipNow()
	}
	file := "testdata/enwik9.zst"
	const wantSize = 1000000000
	if _, err := os.Stat(file); os.IsNotExist(err) {
		t.Skip("To run extended tests, download http://mattmahoney.net/dc/enwik9.zip unzip it \n" +
			"compress it with 'zstd -15 -T0 enwik9' and place it in " + file)
	}
	f, err := os.Open(file)
	if err != nil {
		t.Fatal(err)
	}
	defer f.Close()
	start := time.Now()
	dec, err := NewReader(f)
	if err != nil {
		t.Fatal(err)
	}
	defer dec.Close()
	n, err := io.Copy(io.Discard, dec)
	if err != nil {
		t.Fatal(err)
	}
	if n != wantSize {
		t.Errorf("want size %d, got size %d", wantSize, n)
	}
	elapsed := time.Since(start)
	mbpersec := (float64(n) / (1024 * 1024)) / (float64(elapsed) / (float64(time.Second)))
	t.Logf("Decoded %d bytes with %f.2 MB/s", n, mbpersec)
}

func TestNewDecoderSmallFile(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	file := "testdata/z000028.zst"
	const wantSize = 39807
	f, err := os.Open(file)
	if err != nil {
		t.Fatal(err)
	}
	defer f.Close()
	start := time.Now()
	dec, err := NewReader(f)
	if err != nil {
		t.Fatal(err)
	}
	defer dec.Close()
	n, err := io.Copy(io.Discard, dec)
	if err != nil {
		t.Fatal(err)
	}
	if n != wantSize {
		t.Errorf("want size %d, got size %d", wantSize, n)
	}
	mbpersec := (float64(n) / (1024 * 1024)) / (float64(time.Since(start)) / (float64(time.Second)))
	t.Logf("Decoded %d bytes with %f.2 MB/s", n, mbpersec)
}

type readAndBlock struct {
	buf     []byte
	unblock chan struct{}
}

func (r *readAndBlock) Read(p []byte) (int, error) {
	n := copy(p, r.buf)
	if n == 0 {
		<-r.unblock
		return 0, io.EOF
	}
	r.buf = r.buf[n:]
	return n, nil
}

func TestNewDecoderFlushed(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	file := "testdata/z000028.zst"
	payload, err := os.ReadFile(file)
	if err != nil {
		t.Fatal(err)
	}
	payload = append(payload, payload...) //2x
	payload = append(payload, payload...) //4x
	payload = append(payload, payload...) //8x
	rng := rand.New(rand.NewSource(0x1337))
	runs := 100
	if testing.Short() {
		runs = 5
	}
	enc, err := NewWriter(nil, WithWindowSize(128<<10))
	if err != nil {
		t.Fatal(err)
	}
	defer enc.Close()
	for i := 0; i < runs; i++ {
		wantSize := rng.Intn(len(payload)-1) + 1
		t.Run(fmt.Sprint("size-", wantSize), func(t *testing.T) {
			var encoded bytes.Buffer
			enc.Reset(&encoded)
			_, err := enc.Write(payload[:wantSize])
			if err != nil {
				t.Fatal(err)
			}
			err = enc.Flush()
			if err != nil {
				t.Fatal(err)
			}

			// We must be able to read back up until the flush...
			r := readAndBlock{
				buf:     encoded.Bytes(),
				unblock: make(chan struct{}),
			}
			defer timeout(5 * time.Second)()
			dec, err := NewReader(&r)
			if err != nil {
				t.Fatal(err)
			}
			defer dec.Close()
			defer close(r.unblock)
			readBack := 0
			dst := make([]byte, 1024)
			for readBack < wantSize {
				// Read until we have enough.
				n, err := dec.Read(dst)
				if err != nil {
					t.Fatal(err)
				}
				readBack += n
			}
		})
	}
}

func TestDecoderRegression(t *testing.T) {
	defer timeout(160 * time.Second)()

	zr := testCreateZipReader("testdata/regression.zip", t)
	dec, err := NewReader(nil, WithDecoderConcurrency(1), WithDecoderLowmem(true), WithDecoderMaxMemory(1<<20))
	if err != nil {
		t.Error(err)
		return
	}
	defer dec.Close()
	for i, tt := range zr.File {
		if testing.Short() && i > 10 {
			continue
		}
		t.Run("Reader-"+tt.Name, func(t *testing.T) {
			r, err := tt.Open()
			if err != nil {
				t.Error(err)
				return
			}
			err = dec.Reset(r)
			if err != nil {
				t.Error(err)
				return
			}
			got, gotErr := io.ReadAll(dec)
			t.Log("Received:", len(got), gotErr)

			// Check a fresh instance
			r, err = tt.Open()
			if err != nil {
				t.Error(err)
				return
			}
			decL, err := NewReader(r, WithDecoderConcurrency(1), WithDecoderLowmem(true), WithDecoderMaxMemory(1<<20))
			if err != nil {
				t.Error(err)
				return
			}
			defer decL.Close()
			got2, gotErr2 := io.ReadAll(decL)
			t.Log("Fresh Reader received:", len(got2), gotErr2)
			if gotErr != gotErr2 {
				if gotErr != nil && gotErr2 != nil && gotErr.Error() != gotErr2.Error() {
					t.Error(gotErr, "!=", gotErr2)
				}
				if (gotErr == nil) != (gotErr2 == nil) {
					t.Error(gotErr, "!=", gotErr2)
				}
			}
			if !bytes.Equal(got2, got) {
				if gotErr != nil {
					t.Log("Buffer mismatch without Reset")
				} else {
					t.Error("Buffer mismatch without Reset")
				}
			}
		})
		t.Run("DecodeAll-"+tt.Name, func(t *testing.T) {
			r, err := tt.Open()
			if err != nil {
				t.Error(err)
				return
			}
			in, err := io.ReadAll(r)
			if err != nil {
				t.Error(err)
			}
			got, gotErr := dec.DecodeAll(in, make([]byte, 0, len(in)))
			t.Log("Received:", len(got), gotErr)

			// Check if we got the same:
			decL, err := NewReader(nil, WithDecoderConcurrency(1), WithDecoderLowmem(true), WithDecoderMaxMemory(1<<20))
			if err != nil {
				t.Error(err)
				return
			}
			defer decL.Close()
			got2, gotErr2 := decL.DecodeAll(in, make([]byte, 0, len(in)/2))
			t.Log("Fresh Reader received:", len(got2), gotErr2)
			if gotErr != gotErr2 {
				if gotErr != nil && gotErr2 != nil && gotErr.Error() != gotErr2.Error() {
					t.Error(gotErr, "!=", gotErr2)
				}
				if (gotErr == nil) != (gotErr2 == nil) {
					t.Error(gotErr, "!=", gotErr2)
				}
			}
			if !bytes.Equal(got2, got) {
				if gotErr != nil {
					t.Log("Buffer mismatch without Reset")
				} else {
					t.Error("Buffer mismatch without Reset")
				}
			}
		})
		t.Run("Match-"+tt.Name, func(t *testing.T) {
			r, err := tt.Open()
			if err != nil {
				t.Error(err)
				return
			}
			in, err := io.ReadAll(r)
			if err != nil {
				t.Error(err)
			}
			got, gotErr := dec.DecodeAll(in, make([]byte, 0, len(in)))
			t.Log("Received:", len(got), gotErr)

			// Check a fresh instance
			decL, err := NewReader(bytes.NewBuffer(in), WithDecoderConcurrency(1), WithDecoderLowmem(true), WithDecoderMaxMemory(1<<20))
			if err != nil {
				t.Error(err)
				return
			}
			defer decL.Close()
			got2, gotErr2 := io.ReadAll(decL)
			t.Log("Reader Reader received:", len(got2), gotErr2)
			if gotErr != gotErr2 {
				if gotErr != nil && gotErr2 != nil && gotErr.Error() != gotErr2.Error() {
					t.Error(gotErr, "!=", gotErr2)
				}
				if (gotErr == nil) != (gotErr2 == nil) {
					t.Error(gotErr, "!=", gotErr2)
				}
			}
			if !bytes.Equal(got2, got) {
				if gotErr != nil {
					t.Log("Buffer mismatch")
				} else {
					t.Error("Buffer mismatch")
				}
			}
		})
	}
}

func TestShort(t *testing.T) {
	for _, in := range []string{"f", "fo", "foo"} {
		inb := []byte(in)
		dec, err := NewReader(nil)
		if err != nil {
			t.Fatal(err)
		}
		defer dec.Close()

		t.Run(fmt.Sprintf("DecodeAll-%d", len(in)), func(t *testing.T) {
			_, err := dec.DecodeAll(inb, nil)
			if err == nil {
				t.Error("want error, got nil")
			}
		})
		t.Run(fmt.Sprintf("Reader-%d", len(in)), func(t *testing.T) {
			dec.Reset(bytes.NewReader(inb))
			_, err := io.Copy(io.Discard, dec)
			if err == nil {
				t.Error("want error, got nil")
			}
		})
	}
}

func TestDecoder_Reset(t *testing.T) {
	in, err := os.ReadFile("testdata/z000028")
	if err != nil {
		t.Fatal(err)
	}
	in = append(in, in...)
	var e Encoder
	start := time.Now()
	dst := e.EncodeAll(in, nil)
	t.Log("Simple Encoder len", len(in), "-> zstd len", len(dst))
	mbpersec := (float64(len(in)) / (1024 * 1024)) / (float64(time.Since(start)) / (float64(time.Second)))
	t.Logf("Encoded %d bytes with %.2f MB/s", len(in), mbpersec)

	dec, err := NewReader(nil)
	if err != nil {
		t.Fatal(err)
	}
	defer dec.Close()
	decoded, err := dec.DecodeAll(dst, nil)
	if err != nil {
		t.Error(err, len(decoded))
	}
	if !bytes.Equal(decoded, in) {
		t.Logf("size = %d, got = %d", len(decoded), len(in))
		t.Fatal("Decoded does not match")
	}
	t.Log("Encoded content matched")

	// Decode using reset+copy
	for i := 0; i < 3; i++ {
		err = dec.Reset(bytes.NewBuffer(dst))
		if err != nil {
			t.Fatal(err)
		}
		var dBuf bytes.Buffer
		n, err := io.Copy(&dBuf, dec)
		if err != nil {
			t.Fatal(err)
		}
		decoded = dBuf.Bytes()
		if int(n) != len(decoded) {
			t.Fatalf("decoded reported length mismatch %d != %d", n, len(decoded))
		}
		if !bytes.Equal(decoded, in) {
			os.WriteFile("testdata/"+t.Name()+"-z000028.got", decoded, os.ModePerm)
			os.WriteFile("testdata/"+t.Name()+"-z000028.want", in, os.ModePerm)
			t.Fatal("Decoded does not match")
		}
	}
	// Test without WriterTo interface support.
	for i := 0; i < 3; i++ {
		err = dec.Reset(bytes.NewBuffer(dst))
		if err != nil {
			t.Fatal(err)
		}
		decoded, err := io.ReadAll(io.NopCloser(dec))
		if err != nil {
			t.Fatal(err)
		}
		if !bytes.Equal(decoded, in) {
			os.WriteFile("testdata/"+t.Name()+"-z000028.got", decoded, os.ModePerm)
			os.WriteFile("testdata/"+t.Name()+"-z000028.want", in, os.ModePerm)
			t.Fatal("Decoded does not match")
		}
	}
}

func TestDecoderMultiFrame(t *testing.T) {
	zr := testCreateZipReader("testdata/benchdecoder.zip", t)
	dec, err := NewReader(nil)
	if err != nil {
		t.Fatal(err)
		return
	}
	defer dec.Close()
	for _, tt := range zr.File {
		if !strings.HasSuffix(tt.Name, ".zst") {
			continue
		}
		t.Run(tt.Name, func(t *testing.T) {
			r, err := tt.Open()
			if err != nil {
				t.Fatal(err)
			}
			defer r.Close()
			in, err := io.ReadAll(r)
			if err != nil {
				t.Fatal(err)
			}
			// 2x
			in = append(in, in...)
			if !testing.Short() {
				// 4x
				in = append(in, in...)
				// 8x
				in = append(in, in...)
			}
			err = dec.Reset(bytes.NewBuffer(in))
			if err != nil {
				t.Fatal(err)
			}
			got, err := io.ReadAll(dec)
			if err != nil {
				t.Fatal(err)
			}
			err = dec.Reset(bytes.NewBuffer(in))
			if err != nil {
				t.Fatal(err)
			}
			got2, err := io.ReadAll(dec)
			if err != nil {
				t.Fatal(err)
			}
			if !bytes.Equal(got, got2) {
				t.Error("results mismatch")
			}
		})
	}
}

func TestDecoderMultiFrameReset(t *testing.T) {
	zr := testCreateZipReader("testdata/benchdecoder.zip", t)
	dec, err := NewReader(nil)
	if err != nil {
		t.Fatal(err)
		return
	}
	rng := rand.New(rand.NewSource(1337))
	defer dec.Close()
	for _, tt := range zr.File {
		if !strings.HasSuffix(tt.Name, ".zst") {
			continue
		}
		t.Run(tt.Name, func(t *testing.T) {
			r, err := tt.Open()
			if err != nil {
				t.Fatal(err)
			}
			defer r.Close()
			in, err := io.ReadAll(r)
			if err != nil {
				t.Fatal(err)
			}
			// 2x
			in = append(in, in...)
			if !testing.Short() {
				// 4x
				in = append(in, in...)
				// 8x
				in = append(in, in...)
			}
			err = dec.Reset(bytes.NewBuffer(in))
			if err != nil {
				t.Fatal(err)
			}
			got, err := io.ReadAll(dec)
			if err != nil {
				t.Fatal(err)
			}
			err = dec.Reset(bytes.NewBuffer(in))
			if err != nil {
				t.Fatal(err)
			}
			// Read a random number of bytes
			tmp := make([]byte, rng.Intn(len(got)))
			_, err = io.ReadAtLeast(dec, tmp, len(tmp))
			if err != nil {
				t.Fatal(err)
			}
			err = dec.Reset(bytes.NewBuffer(in))
			if err != nil {
				t.Fatal(err)
			}
			got2, err := io.ReadAll(dec)
			if err != nil {
				t.Fatal(err)
			}
			if !bytes.Equal(got, got2) {
				t.Error("results mismatch")
			}
		})
	}
}

func testDecoderFile(t *testing.T, fn string, newDec func() (*Decoder, error)) {
	zr := testCreateZipReader(fn, t)
	var want = make(map[string][]byte)
	for _, tt := range zr.File {
		if strings.HasSuffix(tt.Name, ".zst") {
			continue
		}
		r, err := tt.Open()
		if err != nil {
			t.Fatal(err)
			return
		}
		want[tt.Name+".zst"], _ = io.ReadAll(r)
	}

	dec, err := newDec()
	if err != nil {
		t.Error(err)
		return
	}
	defer dec.Close()
	for i, tt := range zr.File {
		if !strings.HasSuffix(tt.Name, ".zst") || (testing.Short() && i > 20) {
			continue
		}
		t.Run("Reader-"+tt.Name, func(t *testing.T) {
			defer timeout(10 * time.Second)()
			r, err := tt.Open()
			if err != nil {
				t.Error(err)
				return
			}
			data, err := io.ReadAll(r)
			r.Close()
			if err != nil {
				t.Error(err)
				return
			}
			err = dec.Reset(io.NopCloser(bytes.NewBuffer(data)))
			if err != nil {
				t.Error(err)
				return
			}
			var got []byte
			var gotError error
			var wg sync.WaitGroup
			wg.Add(1)
			go func() {
				got, gotError = io.ReadAll(dec)
				wg.Done()
			}()

			// This decode should not interfere with the stream...
			gotDecAll, err := dec.DecodeAll(data, nil)
			if err != nil {
				t.Error(err)
				if err != ErrCRCMismatch {
					wg.Wait()
					return
				}
			}
			wg.Wait()
			if gotError != nil {
				t.Error(gotError, err)
				if err != ErrCRCMismatch {
					return
				}
			}

			wantB := want[tt.Name]

			compareWith := func(got []byte, displayName, name string) bool {
				if bytes.Equal(wantB, got) {
					return false
				}

				if len(wantB)+len(got) < 1000 {
					t.Logf(" got: %v\nwant: %v", got, wantB)
				} else {
					fileName, _ := filepath.Abs(filepath.Join("testdata", t.Name()+"-want.bin"))
					_ = os.MkdirAll(filepath.Dir(fileName), os.ModePerm)
					err := os.WriteFile(fileName, wantB, os.ModePerm)
					t.Log("Wrote file", fileName, err)

					fileName, _ = filepath.Abs(filepath.Join("testdata", t.Name()+"-"+name+".bin"))
					_ = os.MkdirAll(filepath.Dir(fileName), os.ModePerm)
					err = os.WriteFile(fileName, got, os.ModePerm)
					t.Log("Wrote file", fileName, err)
				}
				t.Logf("Length, want: %d, got: %d", len(wantB), len(got))
				t.Errorf("%s mismatch", displayName)
				return true
			}

			if compareWith(got, "Output", "got") {
				return
			}

			if compareWith(gotDecAll, "DecodeAll Output", "decoded") {
				return
			}

			t.Log(len(got), "bytes returned, matches input, ok!")
		})
	}
}

func testDecoderFileBad(t *testing.T, fn string, newDec func() (*Decoder, error), errMap map[string]string) {
	zr := testCreateZipReader(fn, t)
	var want = make(map[string][]byte)
	for _, tt := range zr.File {
		if strings.HasSuffix(tt.Name, ".zst") {
			continue
		}
		r, err := tt.Open()
		if err != nil {
			t.Fatal(err)
			return
		}
		want[tt.Name+".zst"], _ = io.ReadAll(r)
	}

	dec, err := newDec()
	if err != nil {
		t.Error(err)
		return
	}
	defer dec.Close()
	for _, tt := range zr.File {
		t.Run(tt.Name, func(t *testing.T) {
			defer timeout(10 * time.Second)()
			r, err := tt.Open()
			if err != nil {
				t.Error(err)
				return
			}
			defer r.Close()
			err = dec.Reset(r)
			if err != nil {
				t.Error(err)
				return
			}
			got, err := io.ReadAll(dec)
			if err == ErrCRCMismatch && !strings.Contains(tt.Name, "badsum") {
				t.Error(err)
				return
			}
			if err == nil {
				want := errMap[tt.Name]
				if want == "" {
					want = "<error>"
				}
				t.Error("Did not get expected error", want, "- got", len(got), "bytes")
				return
			}
			if errMap[tt.Name] == "" {
				errMap[tt.Name] = err.Error()
			} else {
				want := errMap[tt.Name]
				if want != err.Error() {
					t.Errorf("error mismatch, prev run got %s, now got %s", want, err.Error())
				}
				return
			}
			t.Log("got error", err)
		})
	}
}

func BenchmarkDecoder_DecoderSmall(b *testing.B) {
	zr := testCreateZipReader("testdata/benchdecoder.zip", b)
	dec, err := NewReader(nil, WithDecodeBuffersBelow(1<<30))
	if err != nil {
		b.Fatal(err)
		return
	}
	defer dec.Close()
	dec2, err := NewReader(nil, WithDecodeBuffersBelow(0))
	if err != nil {
		b.Fatal(err)
		return
	}
	defer dec2.Close()
	for _, tt := range zr.File {
		if !strings.HasSuffix(tt.Name, ".zst") {
			continue
		}
		b.Run(tt.Name, func(b *testing.B) {
			r, err := tt.Open()
			if err != nil {
				b.Fatal(err)
			}
			defer r.Close()
			in, err := io.ReadAll(r)
			if err != nil {
				b.Fatal(err)
			}
			// 2x
			in = append(in, in...)
			// 4x
			in = append(in, in...)
			// 8x
			in = append(in, in...)

			err = dec.Reset(bytes.NewBuffer(in))
			if err != nil {
				b.Fatal(err)
			}
			got, err := io.ReadAll(dec)
			if err != nil {
				b.Fatal(err)
			}
			b.Run("buffered", func(b *testing.B) {
				b.SetBytes(int64(len(got)))
				b.ReportAllocs()
				b.ResetTimer()
				for i := 0; i < b.N; i++ {
					err = dec.Reset(bytes.NewBuffer(in))
					if err != nil {
						b.Fatal(err)
					}
					n, err := io.Copy(io.Discard, dec)
					if err != nil {
						b.Fatal(err)
					}
					if int(n) != len(got) {
						b.Fatalf("want %d, got %d", len(got), n)
					}

				}
			})
			b.Run("unbuffered", func(b *testing.B) {
				b.SetBytes(int64(len(got)))
				b.ReportAllocs()
				b.ResetTimer()
				for i := 0; i < b.N; i++ {
					err = dec2.Reset(bytes.NewBuffer(in))
					if err != nil {
						b.Fatal(err)
					}
					n, err := io.Copy(io.Discard, dec2)
					if err != nil {
						b.Fatal(err)
					}
					if int(n) != len(got) {
						b.Fatalf("want %d, got %d", len(got), n)
					}
				}
			})
		})
	}
}

func BenchmarkDecoder_DecoderReset(b *testing.B) {
	zr := testCreateZipReader("testdata/benchdecoder.zip", b)
	dec, err := NewReader(nil, WithDecodeBuffersBelow(0))
	if err != nil {
		b.Fatal(err)
		return
	}
	defer dec.Close()
	bench := func(name string, b *testing.B, opts []DOption, in, want []byte) {
		b.Helper()
		buf := newBytesReader(in)
		dec, err := NewReader(nil, opts...)
		if err != nil {
			b.Fatal(err)
			return
		}
		defer dec.Close()
		b.Run(name, func(b *testing.B) {
			b.SetBytes(1)
			b.ReportAllocs()
			b.ResetTimer()
			for i := 0; i < b.N; i++ {
				buf.Reset(in)
				err = dec.Reset(buf)
				if err != nil {
					b.Fatal(err)
				}
			}
		})
	}
	for _, tt := range zr.File {
		if !strings.HasSuffix(tt.Name, ".zst") {
			continue
		}
		b.Run(tt.Name, func(b *testing.B) {
			r, err := tt.Open()
			if err != nil {
				b.Fatal(err)
			}
			defer r.Close()
			in, err := io.ReadAll(r)
			if err != nil {
				b.Fatal(err)
			}

			got, err := dec.DecodeAll(in, nil)
			if err != nil {
				b.Fatal(err)
			}
			// Disable buffers:
			bench("stream", b, []DOption{WithDecodeBuffersBelow(0)}, in, got)
			bench("stream-single", b, []DOption{WithDecodeBuffersBelow(0), WithDecoderConcurrency(1)}, in, got)
			// Force buffers:
			bench("buffer", b, []DOption{WithDecodeBuffersBelow(1 << 30)}, in, got)
			bench("buffer-single", b, []DOption{WithDecodeBuffersBelow(1 << 30), WithDecoderConcurrency(1)}, in, got)
		})
	}
}

// newBytesReader returns a *bytes.Reader that also supports Bytes() []byte
func newBytesReader(b []byte) *bytesReader {
	return &bytesReader{Reader: bytes.NewReader(b), buf: b}
}

type bytesReader struct {
	*bytes.Reader
	buf []byte
}

func (b *bytesReader) Bytes() []byte {
	n := b.Reader.Len()
	if n > len(b.buf) {
		panic("buffer mismatch")
	}
	return b.buf[len(b.buf)-n:]
}

func (b *bytesReader) Reset(data []byte) {
	b.buf = data
	b.Reader.Reset(data)
}

func BenchmarkDecoder_DecoderNewNoRead(b *testing.B) {
	zr := testCreateZipReader("testdata/benchdecoder.zip", b)
	dec, err := NewReader(nil)
	if err != nil {
		b.Fatal(err)
		return
	}
	defer dec.Close()

	bench := func(name string, b *testing.B, opts []DOption, in, want []byte) {
		b.Helper()
		b.Run(name, func(b *testing.B) {
			buf := newBytesReader(in)
			b.SetBytes(1)
			b.ReportAllocs()
			b.ResetTimer()
			for i := 0; i < b.N; i++ {
				buf.Reset(in)
				dec, err := NewReader(buf, opts...)
				if err != nil {
					b.Fatal(err)
					return
				}
				dec.Close()
			}
		})
	}
	for _, tt := range zr.File {
		if !strings.HasSuffix(tt.Name, ".zst") {
			continue
		}
		b.Run(tt.Name, func(b *testing.B) {
			r, err := tt.Open()
			if err != nil {
				b.Fatal(err)
			}
			defer r.Close()
			in, err := io.ReadAll(r)
			if err != nil {
				b.Fatal(err)
			}

			got, err := dec.DecodeAll(in, nil)
			if err != nil {
				b.Fatal(err)
			}
			// Disable buffers:
			bench("stream", b, []DOption{WithDecodeBuffersBelow(0)}, in, got)
			bench("stream-single", b, []DOption{WithDecodeBuffersBelow(0), WithDecoderConcurrency(1)}, in, got)
			// Force buffers:
			bench("buffer", b, []DOption{WithDecodeBuffersBelow(1 << 30)}, in, got)
			bench("buffer-single", b, []DOption{WithDecodeBuffersBelow(1 << 30), WithDecoderConcurrency(1)}, in, got)
		})
	}
}

func BenchmarkDecoder_DecoderNewSomeRead(b *testing.B) {
	var buf [1 << 20]byte
	bench := func(name string, b *testing.B, opts []DOption, in *os.File) {
		b.Helper()
		b.Run(name, func(b *testing.B) {
			//b.ReportAllocs()
			b.ResetTimer()
			var heapTotal int64
			var m runtime.MemStats
			for i := 0; i < b.N; i++ {
				runtime.GC()
				runtime.ReadMemStats(&m)
				heapTotal -= int64(m.HeapInuse)
				_, err := in.Seek(io.SeekStart, 0)
				if err != nil {
					b.Fatal(err)
				}

				dec, err := NewReader(in, opts...)
				if err != nil {
					b.Fatal(err)
				}
				// Read 16 MB
				_, err = io.CopyBuffer(io.Discard, io.LimitReader(dec, 16<<20), buf[:])
				if err != nil {
					b.Fatal(err)
				}
				runtime.GC()
				runtime.ReadMemStats(&m)
				heapTotal += int64(m.HeapInuse)

				dec.Close()
			}
			b.ReportMetric(float64(heapTotal)/float64(b.N), "b/op")
		})
	}
	files := []string{"testdata/000002.map.win32K.zst", "testdata/000002.map.win1MB.zst", "testdata/000002.map.win8MB.zst"}
	for _, file := range files {
		if !strings.HasSuffix(file, ".zst") {
			continue
		}
		r, err := os.Open(file)
		if err != nil {
			b.Fatal(err)
		}
		defer r.Close()

		b.Run(file, func(b *testing.B) {
			bench("stream-single", b, []DOption{WithDecodeBuffersBelow(0), WithDecoderConcurrency(1)}, r)
			bench("stream-single-himem", b, []DOption{WithDecodeBuffersBelow(0), WithDecoderConcurrency(1), WithDecoderLowmem(false)}, r)
		})
	}
}

func BenchmarkDecoder_DecodeAll(b *testing.B) {
	zr := testCreateZipReader("testdata/benchdecoder.zip", b)
	dec, err := NewReader(nil, WithDecoderConcurrency(1))
	if err != nil {
		b.Fatal(err)
		return
	}
	defer dec.Close()
	for _, tt := range zr.File {
		if !strings.HasSuffix(tt.Name, ".zst") {
			continue
		}
		b.Run(tt.Name, func(b *testing.B) {
			r, err := tt.Open()
			if err != nil {
				b.Fatal(err)
			}
			defer r.Close()
			in, err := io.ReadAll(r)
			if err != nil {
				b.Fatal(err)
			}
			got, err := dec.DecodeAll(in, nil)
			if err != nil {
				b.Fatal(err)
			}
			b.SetBytes(int64(len(got)))
			b.ReportAllocs()
			b.ResetTimer()
			for i := 0; i < b.N; i++ {
				_, err = dec.DecodeAll(in, got[:0])
				if err != nil {
					b.Fatal(err)
				}
			}
		})
	}
}

func BenchmarkDecoder_DecodeAllFiles(b *testing.B) {
	filepath.Walk("../testdata/", func(path string, info os.FileInfo, err error) error {
		if info.IsDir() || info.Size() < 100 {
			return nil
		}
		b.Run(filepath.Base(path), func(b *testing.B) {
			raw, err := os.ReadFile(path)
			if err != nil {
				b.Error(err)
			}
			for i := SpeedFastest; i <= SpeedBestCompression; i++ {
				if testing.Short() && i > SpeedFastest {
					break
				}
				b.Run(i.String(), func(b *testing.B) {
					enc, err := NewWriter(nil, WithEncoderLevel(i), WithSingleSegment(true))
					if err != nil {
						b.Error(err)
					}
					encoded := enc.EncodeAll(raw, nil)
					if err != nil {
						b.Error(err)
					}
					dec, err := NewReader(nil, WithDecoderConcurrency(1))
					if err != nil {
						b.Error(err)
					}
					decoded, err := dec.DecodeAll(encoded, nil)
					if err != nil {
						b.Error(err)
					}
					b.SetBytes(int64(len(raw)))
					b.ReportAllocs()
					b.ResetTimer()
					for i := 0; i < b.N; i++ {
						decoded, err = dec.DecodeAll(encoded, decoded[:0])
						if err != nil {
							b.Error(err)
						}
					}
					b.ReportMetric(100*float64(len(encoded))/float64(len(raw)), "pct")
				})
			}
		})
		return nil
	})
}

func BenchmarkDecoder_DecodeAllFilesP(b *testing.B) {
	filepath.Walk("../testdata/", func(path string, info os.FileInfo, err error) error {
		if info.IsDir() || info.Size() < 100 {
			return nil
		}
		b.Run(filepath.Base(path), func(b *testing.B) {
			raw, err := os.ReadFile(path)
			if err != nil {
				b.Error(err)
			}
			for i := SpeedFastest; i <= SpeedBestCompression; i++ {
				if testing.Short() && i > SpeedFastest {
					break
				}
				b.Run(i.String(), func(b *testing.B) {
					enc, err := NewWriter(nil, WithEncoderLevel(i), WithSingleSegment(true))
					if err != nil {
						b.Error(err)
					}
					encoded := enc.EncodeAll(raw, nil)
					if err != nil {
						b.Error(err)
					}
					dec, err := NewReader(nil, WithDecoderConcurrency(0))
					if err != nil {
						b.Error(err)
					}
					raw, err := dec.DecodeAll(encoded, nil)
					if err != nil {
						b.Error(err)
					}

					b.SetBytes(int64(len(raw)))
					b.ReportAllocs()
					b.ResetTimer()
					b.RunParallel(func(pb *testing.PB) {
						buf := make([]byte, cap(raw))
						var err error
						for pb.Next() {
							buf, err = dec.DecodeAll(encoded, buf[:0])
							if err != nil {
								b.Error(err)
							}
						}
					})
					b.ReportMetric(100*float64(len(encoded))/float64(len(raw)), "pct")
				})
			}
		})
		return nil
	})
}

func BenchmarkDecoder_DecodeAllParallel(b *testing.B) {
	zr := testCreateZipReader("testdata/benchdecoder.zip", b)
	dec, err := NewReader(nil, WithDecoderConcurrency(runtime.GOMAXPROCS(0)))
	if err != nil {
		b.Fatal(err)
		return
	}
	defer dec.Close()
	for _, tt := range zr.File {
		if !strings.HasSuffix(tt.Name, ".zst") {
			continue
		}
		b.Run(tt.Name, func(b *testing.B) {
			r, err := tt.Open()
			if err != nil {
				b.Fatal(err)
			}
			defer r.Close()
			in, err := io.ReadAll(r)
			if err != nil {
				b.Fatal(err)
			}
			got, err := dec.DecodeAll(in, nil)
			if err != nil {
				b.Fatal(err)
			}
			b.SetBytes(int64(len(got)))
			b.ReportAllocs()
			b.ResetTimer()
			b.RunParallel(func(pb *testing.PB) {
				got := make([]byte, cap(got))
				for pb.Next() {
					_, err = dec.DecodeAll(in, got[:0])
					if err != nil {
						b.Fatal(err)
					}
				}
			})
			b.ReportMetric(100*float64(len(in))/float64(len(got)), "pct")
		})
	}
}

func benchmarkDecoderWithFile(path string, b *testing.B) {
	_, err := os.Stat(path)
	if err != nil {
		if os.IsNotExist(err) {
			b.Skipf("Missing %s", path)
			return
		}
		b.Fatal(err)
	}

	data, err := os.ReadFile(path)
	if err != nil {
		b.Fatal(err)
	}
	dec, err := NewReader(bytes.NewBuffer(data), WithDecoderLowmem(false), WithDecoderConcurrency(1))
	if err != nil {
		b.Fatal(err)
	}
	n, err := io.Copy(io.Discard, dec)
	if err != nil {
		b.Fatal(err)
	}

	b.Run("multithreaded-writer", func(b *testing.B) {
		dec, err := NewReader(nil, WithDecoderLowmem(true))
		if err != nil {
			b.Fatal(err)
		}
		b.SetBytes(n)
		b.ReportAllocs()
		b.ResetTimer()
		for i := 0; i < b.N; i++ {
			err = dec.Reset(bytes.NewBuffer(data))
			if err != nil {
				b.Fatal(err)
			}
			_, err := io.CopyN(io.Discard, dec, n)
			if err != nil {
				b.Fatal(err)
			}
		}
	})

	b.Run("multithreaded-writer-himem", func(b *testing.B) {
		dec, err := NewReader(nil, WithDecoderLowmem(false))
		if err != nil {
			b.Fatal(err)
		}

		b.SetBytes(n)
		b.ReportAllocs()
		b.ResetTimer()
		for i := 0; i < b.N; i++ {
			err = dec.Reset(bytes.NewBuffer(data))
			if err != nil {
				b.Fatal(err)
			}
			_, err := io.CopyN(io.Discard, dec, n)
			if err != nil {
				b.Fatal(err)
			}
		}
	})

	b.Run("singlethreaded-writer", func(b *testing.B) {
		dec, err := NewReader(nil, WithDecoderConcurrency(1), WithDecoderLowmem(true))
		if err != nil {
			b.Fatal(err)
		}

		b.SetBytes(n)
		b.ReportAllocs()
		b.ResetTimer()
		for i := 0; i < b.N; i++ {
			err = dec.Reset(bytes.NewBuffer(data))
			if err != nil {
				b.Fatal(err)
			}
			_, err := io.CopyN(io.Discard, dec, n)
			if err != nil {
				b.Fatal(err)
			}
		}
	})

	b.Run("singlethreaded-writerto", func(b *testing.B) {
		dec, err := NewReader(nil, WithDecoderConcurrency(1), WithDecoderLowmem(true))
		if err != nil {
			b.Fatal(err)
		}

		b.SetBytes(n)
		b.ReportAllocs()
		b.ResetTimer()
		for i := 0; i < b.N; i++ {
			err = dec.Reset(bytes.NewBuffer(data))
			if err != nil {
				b.Fatal(err)
			}
			// io.Copy will use io.WriterTo
			_, err := io.Copy(io.Discard, dec)
			if err != nil {
				b.Fatal(err)
			}
		}
	})
	b.Run("singlethreaded-himem", func(b *testing.B) {
		dec, err := NewReader(nil, WithDecoderConcurrency(1), WithDecoderLowmem(false))
		if err != nil {
			b.Fatal(err)
		}

		b.SetBytes(n)
		b.ReportAllocs()
		b.ResetTimer()
		for i := 0; i < b.N; i++ {
			err = dec.Reset(bytes.NewBuffer(data))
			if err != nil {
				b.Fatal(err)
			}
			// io.Copy will use io.WriterTo
			_, err := io.Copy(io.Discard, dec)
			if err != nil {
				b.Fatal(err)
			}
		}
	})
}

func BenchmarkDecoderSilesia(b *testing.B) {
	benchmarkDecoderWithFile("testdata/silesia.tar.zst", b)
}

func BenchmarkDecoderEnwik9(b *testing.B) {
	benchmarkDecoderWithFile("testdata/enwik9.zst", b)
}

func BenchmarkDecoderWithCustomFiles(b *testing.B) {
	const info = "To run benchmark on custom .zst files, please place your files in subdirectory 'testdata/benchmark-custom'.\nEach file is tested in a separate benchmark, thus it is possible to select files with the standard command 'go test -bench BenchmarkDecoderWithCustomFiles/<pattern>."

	const subdir = "testdata/benchmark-custom"

	if _, err := os.Stat(subdir); os.IsNotExist(err) {
		b.Skip(info)
	}

	files, err := filepath.Glob(filepath.Join(subdir, "*.zst"))
	if err != nil {
		b.Error(err)
		return
	}

	if len(files) == 0 {
		b.Skip(info)
	}

	for _, path := range files {
		name := filepath.Base(path)
		b.Run(name, func(b *testing.B) { benchmarkDecoderWithFile(path, b) })
	}
}

func testDecoderDecodeAll(t *testing.T, fn string, dec *Decoder) {
	zr := testCreateZipReader(fn, t)
	var want = make(map[string][]byte)
	for _, tt := range zr.File {
		if strings.HasSuffix(tt.Name, ".zst") {
			continue
		}
		r, err := tt.Open()
		if err != nil {
			t.Fatal(err)
			return
		}
		want[tt.Name+".zst"], _ = io.ReadAll(r)
	}
	var wg sync.WaitGroup
	for i, tt := range zr.File {
		tt := tt
		if !strings.HasSuffix(tt.Name, ".zst") || (testing.Short() && i > 20) {
			continue
		}
		wg.Add(1)
		t.Run("DecodeAll-"+tt.Name, func(t *testing.T) {
			defer wg.Done()
			t.Parallel()
			r, err := tt.Open()
			if err != nil {
				t.Fatal(err)
			}
			in, err := io.ReadAll(r)
			if err != nil {
				t.Fatal(err)
			}
			wantB := want[tt.Name]
			// make a buffer that is too small.
			got, err := dec.DecodeAll(in, make([]byte, 10, 200))
			if err != nil {
				t.Error(err)
			}
			if len(got) < 10 {
				t.Fatal("didn't get input back")
			}
			got = got[10:]
			if !bytes.Equal(wantB, got) {
				if len(wantB)+len(got) < 1000 {
					t.Logf(" got: %v\nwant: %v", got, wantB)
				} else {
					fileName, _ := filepath.Abs(filepath.Join("testdata", t.Name()+"-want.bin"))
					_ = os.MkdirAll(filepath.Dir(fileName), os.ModePerm)
					err := os.WriteFile(fileName, wantB, os.ModePerm)
					t.Log("Wrote file", fileName, err)

					fileName, _ = filepath.Abs(filepath.Join("testdata", t.Name()+"-got.bin"))
					_ = os.MkdirAll(filepath.Dir(fileName), os.ModePerm)
					err = os.WriteFile(fileName, got, os.ModePerm)
					t.Log("Wrote file", fileName, err)
				}
				t.Logf("Length, want: %d, got: %d", len(wantB), len(got))
				t.Error("Output mismatch")
				return
			}
			t.Log(len(got), "bytes returned, matches input, ok!")
		})
	}
	go func() {
		wg.Wait()
		dec.Close()
	}()
}

func testDecoderDecodeAllError(t *testing.T, fn string, dec *Decoder, errMap map[string]string) {
	zr := testCreateZipReader(fn, t)

	var wg sync.WaitGroup
	for _, tt := range zr.File {
		tt := tt
		if !strings.HasSuffix(tt.Name, ".zst") {
			continue
		}
		wg.Add(1)
		t.Run(tt.Name, func(t *testing.T) {
			defer wg.Done()
			r, err := tt.Open()
			if err != nil {
				t.Fatal(err)
			}
			in, err := io.ReadAll(r)
			if err != nil {
				t.Fatal(err)
			}
			// make a buffer that is small.
			got, err := dec.DecodeAll(in, make([]byte, 0, 20))
			if err == nil {
				t.Error("Did not get expected error, got", len(got), "bytes")
				return
			}
			t.Log(err)
			if errMap[tt.Name] == "" {
				t.Error("cannot check error")
			} else {
				want := errMap[tt.Name]
				if want != err.Error() {
					if want == ErrFrameSizeMismatch.Error() && err == ErrDecoderSizeExceeded {
						return
					}
					if want == ErrWindowSizeExceeded.Error() && err == ErrDecoderSizeExceeded {
						return
					}
					t.Errorf("error mismatch, prev run got %s, now got %s", want, err.Error())
				}
				return
			}
		})
	}
	go func() {
		wg.Wait()
		dec.Close()
	}()
}

// Test our predefined tables are correct.
// We don't predefine them, since this also tests our transformations.
// Reference from here: https://github.com/facebook/zstd/blob/ededcfca57366461021c922720878c81a5854a0a/lib/decompress/zstd_decompress_block.c#L234
func TestPredefTables(t *testing.T) {
	initPredefined()
	x := func(nextState uint16, nbAddBits, nbBits uint8, baseVal uint32) decSymbol {
		return newDecSymbol(nbBits, nbAddBits, nextState, baseVal)
	}
	for i := range fsePredef[:] {
		var want []decSymbol
		switch tableIndex(i) {
		case tableLiteralLengths:
			want = []decSymbol{
				/* nextState, nbAddBits, nbBits, baseVal */
				x(0, 0, 4, 0), x(16, 0, 4, 0),
				x(32, 0, 5, 1), x(0, 0, 5, 3),
				x(0, 0, 5, 4), x(0, 0, 5, 6),
				x(0, 0, 5, 7), x(0, 0, 5, 9),
				x(0, 0, 5, 10), x(0, 0, 5, 12),
				x(0, 0, 6, 14), x(0, 1, 5, 16),
				x(0, 1, 5, 20), x(0, 1, 5, 22),
				x(0, 2, 5, 28), x(0, 3, 5, 32),
				x(0, 4, 5, 48), x(32, 6, 5, 64),
				x(0, 7, 5, 128), x(0, 8, 6, 256),
				x(0, 10, 6, 1024), x(0, 12, 6, 4096),
				x(32, 0, 4, 0), x(0, 0, 4, 1),
				x(0, 0, 5, 2), x(32, 0, 5, 4),
				x(0, 0, 5, 5), x(32, 0, 5, 7),
				x(0, 0, 5, 8), x(32, 0, 5, 10),
				x(0, 0, 5, 11), x(0, 0, 6, 13),
				x(32, 1, 5, 16), x(0, 1, 5, 18),
				x(32, 1, 5, 22), x(0, 2, 5, 24),
				x(32, 3, 5, 32), x(0, 3, 5, 40),
				x(0, 6, 4, 64), x(16, 6, 4, 64),
				x(32, 7, 5, 128), x(0, 9, 6, 512),
				x(0, 11, 6, 2048), x(48, 0, 4, 0),
				x(16, 0, 4, 1), x(32, 0, 5, 2),
				x(32, 0, 5, 3), x(32, 0, 5, 5),
				x(32, 0, 5, 6), x(32, 0, 5, 8),
				x(32, 0, 5, 9), x(32, 0, 5, 11),
				x(32, 0, 5, 12), x(0, 0, 6, 15),
				x(32, 1, 5, 18), x(32, 1, 5, 20),
				x(32, 2, 5, 24), x(32, 2, 5, 28),
				x(32, 3, 5, 40), x(32, 4, 5, 48),
				x(0, 16, 6, 65536), x(0, 15, 6, 32768),
				x(0, 14, 6, 16384), x(0, 13, 6, 8192),
			}
		case tableOffsets:
			want = []decSymbol{
				/* nextState, nbAddBits, nbBits, baseVal */
				x(0, 0, 5, 0), x(0, 6, 4, 61),
				x(0, 9, 5, 509), x(0, 15, 5, 32765),
				x(0, 21, 5, 2097149), x(0, 3, 5, 5),
				x(0, 7, 4, 125), x(0, 12, 5, 4093),
				x(0, 18, 5, 262141), x(0, 23, 5, 8388605),
				x(0, 5, 5, 29), x(0, 8, 4, 253),
				x(0, 14, 5, 16381), x(0, 20, 5, 1048573),
				x(0, 2, 5, 1), x(16, 7, 4, 125),
				x(0, 11, 5, 2045), x(0, 17, 5, 131069),
				x(0, 22, 5, 4194301), x(0, 4, 5, 13),
				x(16, 8, 4, 253), x(0, 13, 5, 8189),
				x(0, 19, 5, 524285), x(0, 1, 5, 1),
				x(16, 6, 4, 61), x(0, 10, 5, 1021),
				x(0, 16, 5, 65533), x(0, 28, 5, 268435453),
				x(0, 27, 5, 134217725), x(0, 26, 5, 67108861),
				x(0, 25, 5, 33554429), x(0, 24, 5, 16777213),
			}
		case tableMatchLengths:
			want = []decSymbol{
				/* nextState, nbAddBits, nbBits, baseVal */
				x(0, 0, 6, 3), x(0, 0, 4, 4),
				x(32, 0, 5, 5), x(0, 0, 5, 6),
				x(0, 0, 5, 8), x(0, 0, 5, 9),
				x(0, 0, 5, 11), x(0, 0, 6, 13),
				x(0, 0, 6, 16), x(0, 0, 6, 19),
				x(0, 0, 6, 22), x(0, 0, 6, 25),
				x(0, 0, 6, 28), x(0, 0, 6, 31),
				x(0, 0, 6, 34), x(0, 1, 6, 37),
				x(0, 1, 6, 41), x(0, 2, 6, 47),
				x(0, 3, 6, 59), x(0, 4, 6, 83),
				x(0, 7, 6, 131), x(0, 9, 6, 515),
				x(16, 0, 4, 4), x(0, 0, 4, 5),
				x(32, 0, 5, 6), x(0, 0, 5, 7),
				x(32, 0, 5, 9), x(0, 0, 5, 10),
				x(0, 0, 6, 12), x(0, 0, 6, 15),
				x(0, 0, 6, 18), x(0, 0, 6, 21),
				x(0, 0, 6, 24), x(0, 0, 6, 27),
				x(0, 0, 6, 30), x(0, 0, 6, 33),
				x(0, 1, 6, 35), x(0, 1, 6, 39),
				x(0, 2, 6, 43), x(0, 3, 6, 51),
				x(0, 4, 6, 67), x(0, 5, 6, 99),
				x(0, 8, 6, 259), x(32, 0, 4, 4),
				x(48, 0, 4, 4), x(16, 0, 4, 5),
				x(32, 0, 5, 7), x(32, 0, 5, 8),
				x(32, 0, 5, 10), x(32, 0, 5, 11),
				x(0, 0, 6, 14), x(0, 0, 6, 17),
				x(0, 0, 6, 20), x(0, 0, 6, 23),
				x(0, 0, 6, 26), x(0, 0, 6, 29),
				x(0, 0, 6, 32), x(0, 16, 6, 65539),
				x(0, 15, 6, 32771), x(0, 14, 6, 16387),
				x(0, 13, 6, 8195), x(0, 12, 6, 4099),
				x(0, 11, 6, 2051), x(0, 10, 6, 1027),
			}
		}
		pre := fsePredef[i]
		got := pre.dt[:1<<pre.actualTableLog]
		if !reflect.DeepEqual(got, want) {
			t.Logf("want: %v", want)
			t.Logf("got : %v", got)
			t.Errorf("Predefined table %d incorrect, len(got) = %d, len(want) = %d", i, len(got), len(want))
		}
	}
}

func TestResetNil(t *testing.T) {
	dec, err := NewReader(nil)
	if err != nil {
		t.Fatal(err)
	}
	defer dec.Close()

	_, err = io.ReadAll(dec)
	if err != ErrDecoderNilInput {
		t.Fatalf("Expected ErrDecoderNilInput when decoding from a nil reader, got %v", err)
	}

	emptyZstdBlob := []byte{40, 181, 47, 253, 32, 0, 1, 0, 0}

	dec.Reset(bytes.NewBuffer(emptyZstdBlob))

	result, err := io.ReadAll(dec)
	if err != nil && err != io.EOF {
		t.Fatal(err)
	}
	if len(result) != 0 {
		t.Fatalf("Expected to read 0 bytes, actually read %d", len(result))
	}

	dec.Reset(nil)

	_, err = io.ReadAll(dec)
	if err != ErrDecoderNilInput {
		t.Fatalf("Expected ErrDecoderNilInput when decoding from a nil reader, got %v", err)
	}

	dec.Reset(bytes.NewBuffer(emptyZstdBlob))

	result, err = io.ReadAll(dec)
	if err != nil && err != io.EOF {
		t.Fatal(err)
	}
	if len(result) != 0 {
		t.Fatalf("Expected to read 0 bytes, actually read %d", len(result))
	}
}

func TestIgnoreChecksum(t *testing.T) {
	// zstd file containing text "compress\n" and has an xxhash checksum
	zstdBlob := []byte{0x28, 0xb5, 0x2f, 0xfd, 0x24, 0x09, 0x49, 0x00, 0x00, 'C', 'o', 'm', 'p', 'r', 'e', 's', 's', '\n', 0x79, 0x6e, 0xe0, 0xd2}

	// replace letter 'c' with 'C', so decoding should fail.
	zstdBlob[9] = 'C'

	{
		// Check if the file is indeed incorrect
		dec, err := NewReader(nil)
		if err != nil {
			t.Fatal(err)
		}
		defer dec.Close()

		dec.Reset(bytes.NewBuffer(zstdBlob))

		_, err = io.ReadAll(dec)
		if err == nil {
			t.Fatal("Expected decoding error")
		}

		if !errors.Is(err, ErrCRCMismatch) {
			t.Fatalf("Expected checksum error, got '%s'", err)
		}
	}

	{
		// Ignore CRC error and decompress the content
		dec, err := NewReader(nil, IgnoreChecksum(true))
		if err != nil {
			t.Fatal(err)
		}
		defer dec.Close()

		dec.Reset(bytes.NewBuffer(zstdBlob))

		res, err := io.ReadAll(dec)
		if err != nil {
			t.Fatalf("Unexpected error: '%s'", err)
		}

		want := []byte{'C', 'o', 'm', 'p', 'r', 'e', 's', 's', '\n'}
		if !bytes.Equal(res, want) {
			t.Logf("want: %s", want)
			t.Logf("got:  %s", res)
			t.Fatalf("Wrong output")
		}
	}
}

func timeout(after time.Duration) (cancel func()) {
	if isRaceTest {
		return func() {}
	}
	c := time.After(after)
	cc := make(chan struct{})
	go func() {
		select {
		case <-cc:
			return
		case <-c:
			buf := make([]byte, 1<<20)
			stacklen := runtime.Stack(buf, true)
			log.Printf("=== Timeout, assuming deadlock ===\n*** goroutine dump...\n%s\n*** end\n", string(buf[:stacklen]))
			os.Exit(2)
		}
	}()
	return func() {
		close(cc)
	}
}

func TestWithDecodeAllCapLimit(t *testing.T) {
	var encs []*Encoder
	var decs []*Decoder
	addEnc := func(e *Encoder, _ error) {
		encs = append(encs, e)
	}
	addDec := func(d *Decoder, _ error) {
		decs = append(decs, d)
	}
	addEnc(NewWriter(nil, WithZeroFrames(true), WithWindowSize(4<<10)))
	addEnc(NewWriter(nil, WithEncoderConcurrency(1), WithWindowSize(4<<10)))
	addEnc(NewWriter(nil, WithZeroFrames(false), WithWindowSize(4<<10)))
	addEnc(NewWriter(nil, WithWindowSize(128<<10)))
	addDec(NewReader(nil, WithDecodeAllCapLimit(true)))
	addDec(NewReader(nil, WithDecodeAllCapLimit(true), WithDecoderConcurrency(1)))
	addDec(NewReader(nil, WithDecodeAllCapLimit(true), WithDecoderLowmem(true)))
	addDec(NewReader(nil, WithDecodeAllCapLimit(true), WithDecoderMaxWindow(128<<10)))
	addDec(NewReader(nil, WithDecodeAllCapLimit(true), WithDecoderMaxMemory(1<<20)))
	for sz := 0; sz < 1<<20; sz = (sz + 1) * 2 {
		sz := sz
		t.Run(strconv.Itoa(sz), func(t *testing.T) {
			t.Parallel()
			for ei, enc := range encs {
				for di, dec := range decs {
					t.Run(fmt.Sprintf("e%d:d%d", ei, di), func(t *testing.T) {
						encoded := enc.EncodeAll(make([]byte, sz), nil)
						for i := sz - 1; i < sz+1; i++ {
							if i < 0 {
								continue
							}
							const existinglen = 5
							got, err := dec.DecodeAll(encoded, make([]byte, existinglen, i+existinglen))
							if i < sz {
								if err != ErrDecoderSizeExceeded {
									t.Errorf("cap: %d, want %v, got %v", i, ErrDecoderSizeExceeded, err)
								}
							} else {
								if err != nil {
									t.Errorf("cap: %d, want %v, got %v", i, nil, err)
									continue
								}
								if len(got) != existinglen+i {
									t.Errorf("cap: %d, want output size %d, got %d", i, existinglen+i, len(got))
								}
							}
						}
					})
				}
			}
		})
	}
}
