//go:build cgo

package zstd

import (
	"bytes"
	"encoding/hex"
	"fmt"
	"math/rand"
	"testing"

	pure "github.com/klauspost/compress/zstd"
	cgo "github.com/valyala/gozstd"
)

func TestDecomrpessLimitedOK(t *testing.T) {
	f := func(compressedData []byte, limit int) {
		t.Helper()

		_, err := DecompressLimited(nil, compressedData, limit)
		if err != nil {
			t.Fatalf("cannot decompress data with limit=%d: %s", limit, err)
		}
	}

	var bb bytes.Buffer
	for bb.Len() < 12*128*1024 {
		fmt.Fprintf(&bb, "compress/decompress big data %d, ", bb.Len())
	}
	originData := bb.Bytes()
	// block decompression
	cd := CompressLevel(nil, originData, 0)

	// decompressed size matches block limit
	f(cd, len(originData))

	// unlimited
	f(cd, 0)

}

func TestDecompressLimitedFail(t *testing.T) {

	f := func(input []byte, limit int) {
		t.Helper()
		_, err := DecompressLimited(nil, input, limit)
		if err == nil {
			t.Errorf("unexpected nil-error for decompress with limit: %d", limit)
		}

	}

	var bb bytes.Buffer
	for bb.Len() < 12*128*1024 {
		fmt.Fprintf(&bb, "compress/decompress big data %d, ", bb.Len())
	}

	// valid input bigger than limit
	f(bb.Bytes(), 1024)

	input, err := hex.DecodeString("28b52ffd8400005ed0b209000030ecaf4412")
	if err != nil {
		t.Fatalf("BUG: unexpected hex input: %s", err)
	}
	// input with framecontent bigger than actual payload
	f(input, 512)

	// input with stream windowSize bigger than limit
	input, err = hex.DecodeString("28b52ffd04981900003030304e8da22b")
	if err != nil {
		t.Fatalf("BUG: unexpected hex input: %s", err)
	}
	f(input, 8*1e6*10)
}

func TestCompressDecompress(t *testing.T) {
	testCrossCompressDecompress(t, []byte("a"))
	testCrossCompressDecompress(t, []byte("foobarbaz"))

	r := rand.New(rand.NewSource(1))
	var b []byte
	for range 64 * 1024 {
		b = append(b, byte(r.Int31n(256)))
	}
	testCrossCompressDecompress(t, b)
}

func testCrossCompressDecompress(t *testing.T, b []byte) {
	testCompressDecompress(t, pureCompress, pureDecompress, b)
	testCompressDecompress(t, cgoCompress, cgoDecompress, b)
	testCompressDecompress(t, pureCompress, cgoDecompress, b)
	testCompressDecompress(t, cgoCompress, pureDecompress, b)
}

func testCompressDecompress(t *testing.T, compress compressFn, decompress decompressFn, b []byte) {
	bc, err := compress(nil, b, 5)
	if err != nil {
		t.Fatalf("unexpected error when compressing b=%x: %s", b, err)
	}
	bNew, err := decompress(nil, bc)
	if err != nil {
		t.Fatalf("unexpected error when decompressing b=%x from bc=%x: %s", b, bc, err)
	}
	if string(bNew) != string(b) {
		t.Fatalf("invalid bNew; got\n%x; expecting\n%x", bNew, b)
	}

	prefix := []byte{1, 2, 33}
	bcNew, err := compress(prefix, b, 5)
	if err != nil {
		t.Fatalf("unexpected error when compressing b=%x: %s", bcNew, err)
	}
	if string(bcNew[:len(prefix)]) != string(prefix) {
		t.Fatalf("invalid prefix for b=%x; got\n%x; expecting\n%x", b, bcNew[:len(prefix)], prefix)
	}
	if string(bcNew[len(prefix):]) != string(bc) {
		t.Fatalf("invalid prefixed bcNew for b=%x; got\n%x; expecting\n%x", b, bcNew[len(prefix):], bc)
	}

	bNew, err = decompress(prefix, bc)
	if err != nil {
		t.Fatalf("unexpected error when decompressing b=%x from bc=%x with prefix: %s", b, bc, err)
	}
	if string(bNew[:len(prefix)]) != string(prefix) {
		t.Fatalf("invalid bNew prefix when decompressing bc=%x; got\n%x; expecting\n%x", bc, bNew[:len(prefix)], prefix)
	}
	if string(bNew[len(prefix):]) != string(b) {
		t.Fatalf("invalid prefixed bNew; got\n%x; expecting\n%x", bNew[len(prefix):], b)
	}
}

type compressFn func(dst, src []byte, compressionLevel int) ([]byte, error)

func pureCompress(dst, src []byte, _ int) ([]byte, error) {
	w, err := pure.NewWriter(nil,
		pure.WithEncoderCRC(false), // Disable CRC for performance reasons.
		pure.WithEncoderLevel(pure.SpeedBestCompression))
	if err != nil {
		return nil, err
	}
	return w.EncodeAll(src, dst), nil
}

func cgoCompress(dst, src []byte, compressionLevel int) ([]byte, error) {
	return cgo.CompressLevel(dst, src, compressionLevel), nil
}

type decompressFn func(dst, src []byte) ([]byte, error)

func pureDecompress(dst, src []byte) ([]byte, error) {
	decoder, err := pure.NewReader(nil)
	if err != nil {
		return nil, err
	}
	return decoder.DecodeAll(src, dst)
}

func cgoDecompress(dst, src []byte) ([]byte, error) {
	return cgo.Decompress(dst, src)
}
