//go:build !appengine && !noasm && gc

package s2

import (
	"sync"

	"github.com/klauspost/compress/internal/race"
)

const hasAmd64Asm = true

var encPools [4]sync.Pool

// encodeBlock encodes a non-empty src to a guaranteed-large-enough dst. It
// assumes that the varint-encoded length of the decompressed bytes has already
// been written.
//
// It also assumes that:
//
//	len(dst) >= MaxEncodedLen(len(src)) &&
//	minNonLiteralBlockSize <= len(src) && len(src) <= maxBlockSize
func encodeBlock(dst, src []byte) (d int) {
	race.ReadSlice(src)
	race.WriteSlice(dst)

	const (
		// Use 12 bit table when less than...
		limit12B = 16 << 10
		// Use 10 bit table when less than...
		limit10B = 4 << 10
		// Use 8 bit table when less than...
		limit8B = 512
	)

	if len(src) >= 4<<20 {
		const sz, pool = 65536, 0
		tmp, ok := encPools[pool].Get().(*[sz]byte)
		if !ok {
			tmp = &[sz]byte{}
		}
		race.WriteSlice(tmp[:])
		defer encPools[pool].Put(tmp)
		return encodeBlockAsm(dst, src, tmp)
	}
	if len(src) >= limit12B {
		const sz, pool = 65536, 0
		tmp, ok := encPools[pool].Get().(*[sz]byte)
		if !ok {
			tmp = &[sz]byte{}
		}
		race.WriteSlice(tmp[:])
		defer encPools[pool].Put(tmp)
		return encodeBlockAsm4MB(dst, src, tmp)
	}
	if len(src) >= limit10B {
		const sz, pool = 16384, 1
		tmp, ok := encPools[pool].Get().(*[sz]byte)
		if !ok {
			tmp = &[sz]byte{}
		}
		race.WriteSlice(tmp[:])
		defer encPools[pool].Put(tmp)
		return encodeBlockAsm12B(dst, src, tmp)
	}
	if len(src) >= limit8B {
		const sz, pool = 4096, 2
		tmp, ok := encPools[pool].Get().(*[sz]byte)
		if !ok {
			tmp = &[sz]byte{}
		}
		race.WriteSlice(tmp[:])
		defer encPools[pool].Put(tmp)
		return encodeBlockAsm10B(dst, src, tmp)
	}
	if len(src) < minNonLiteralBlockSize {
		return 0
	}
	const sz, pool = 1024, 3
	tmp, ok := encPools[pool].Get().(*[sz]byte)
	if !ok {
		tmp = &[sz]byte{}
	}
	race.WriteSlice(tmp[:])
	defer encPools[pool].Put(tmp)
	return encodeBlockAsm8B(dst, src, tmp)
}

var encBetterPools [5]sync.Pool

// encodeBlockBetter encodes a non-empty src to a guaranteed-large-enough dst. It
// assumes that the varint-encoded length of the decompressed bytes has already
// been written.
//
// It also assumes that:
//
//	len(dst) >= MaxEncodedLen(len(src)) &&
//	minNonLiteralBlockSize <= len(src) && len(src) <= maxBlockSize
func encodeBlockBetter(dst, src []byte) (d int) {
	race.ReadSlice(src)
	race.WriteSlice(dst)

	const (
		// Use 12 bit table when less than...
		limit12B = 16 << 10
		// Use 10 bit table when less than...
		limit10B = 4 << 10
		// Use 8 bit table when less than...
		limit8B = 512
	)

	if len(src) > 4<<20 {
		const sz, pool = 589824, 0
		tmp, ok := encBetterPools[pool].Get().(*[sz]byte)
		if !ok {
			tmp = &[sz]byte{}
		}
		race.WriteSlice(tmp[:])
		defer encBetterPools[pool].Put(tmp)
		return encodeBetterBlockAsm(dst, src, tmp)
	}
	if len(src) >= limit12B {
		const sz, pool = 589824, 0
		tmp, ok := encBetterPools[pool].Get().(*[sz]byte)
		if !ok {
			tmp = &[sz]byte{}
		}
		race.WriteSlice(tmp[:])
		defer encBetterPools[pool].Put(tmp)

		return encodeBetterBlockAsm4MB(dst, src, tmp)
	}
	if len(src) >= limit10B {
		const sz, pool = 81920, 0
		tmp, ok := encBetterPools[pool].Get().(*[sz]byte)
		if !ok {
			tmp = &[sz]byte{}
		}
		race.WriteSlice(tmp[:])
		defer encBetterPools[pool].Put(tmp)

		return encodeBetterBlockAsm12B(dst, src, tmp)
	}
	if len(src) >= limit8B {
		const sz, pool = 20480, 1
		tmp, ok := encBetterPools[pool].Get().(*[sz]byte)
		if !ok {
			tmp = &[sz]byte{}
		}
		race.WriteSlice(tmp[:])
		defer encBetterPools[pool].Put(tmp)
		return encodeBetterBlockAsm10B(dst, src, tmp)
	}
	if len(src) < minNonLiteralBlockSize {
		return 0
	}

	const sz, pool = 5120, 2
	tmp, ok := encBetterPools[pool].Get().(*[sz]byte)
	if !ok {
		tmp = &[sz]byte{}
	}
	race.WriteSlice(tmp[:])
	defer encBetterPools[pool].Put(tmp)
	return encodeBetterBlockAsm8B(dst, src, tmp)
}

// encodeBlockSnappy encodes a non-empty src to a guaranteed-large-enough dst. It
// assumes that the varint-encoded length of the decompressed bytes has already
// been written.
//
// It also assumes that:
//
//	len(dst) >= MaxEncodedLen(len(src)) &&
//	minNonLiteralBlockSize <= len(src) && len(src) <= maxBlockSize
func encodeBlockSnappy(dst, src []byte) (d int) {
	race.ReadSlice(src)
	race.WriteSlice(dst)

	const (
		// Use 12 bit table when less than...
		limit12B = 16 << 10
		// Use 10 bit table when less than...
		limit10B = 4 << 10
		// Use 8 bit table when less than...
		limit8B = 512
	)
	if len(src) > 65536 {
		const sz, pool = 65536, 0
		tmp, ok := encPools[pool].Get().(*[sz]byte)
		if !ok {
			tmp = &[sz]byte{}
		}
		race.WriteSlice(tmp[:])
		defer encPools[pool].Put(tmp)
		return encodeSnappyBlockAsm(dst, src, tmp)
	}
	if len(src) >= limit12B {
		const sz, pool = 65536, 0
		tmp, ok := encPools[pool].Get().(*[sz]byte)
		if !ok {
			tmp = &[sz]byte{}
		}
		race.WriteSlice(tmp[:])
		defer encPools[pool].Put(tmp)
		return encodeSnappyBlockAsm64K(dst, src, tmp)
	}
	if len(src) >= limit10B {
		const sz, pool = 16384, 1
		tmp, ok := encPools[pool].Get().(*[sz]byte)
		if !ok {
			tmp = &[sz]byte{}
		}
		race.WriteSlice(tmp[:])
		defer encPools[pool].Put(tmp)
		return encodeSnappyBlockAsm12B(dst, src, tmp)
	}
	if len(src) >= limit8B {
		const sz, pool = 4096, 2
		tmp, ok := encPools[pool].Get().(*[sz]byte)
		if !ok {
			tmp = &[sz]byte{}
		}
		race.WriteSlice(tmp[:])
		defer encPools[pool].Put(tmp)
		return encodeSnappyBlockAsm10B(dst, src, tmp)
	}
	if len(src) < minNonLiteralBlockSize {
		return 0
	}
	const sz, pool = 1024, 3
	tmp, ok := encPools[pool].Get().(*[sz]byte)
	if !ok {
		tmp = &[sz]byte{}
	}
	race.WriteSlice(tmp[:])
	defer encPools[pool].Put(tmp)
	return encodeSnappyBlockAsm8B(dst, src, tmp)
}

// encodeBlockSnappy encodes a non-empty src to a guaranteed-large-enough dst. It
// assumes that the varint-encoded length of the decompressed bytes has already
// been written.
//
// It also assumes that:
//
//	len(dst) >= MaxEncodedLen(len(src)) &&
//	minNonLiteralBlockSize <= len(src) && len(src) <= maxBlockSize
func encodeBlockBetterSnappy(dst, src []byte) (d int) {
	race.ReadSlice(src)
	race.WriteSlice(dst)

	const (
		// Use 12 bit table when less than...
		limit12B = 16 << 10
		// Use 10 bit table when less than...
		limit10B = 4 << 10
		// Use 8 bit table when less than...
		limit8B = 512
	)
	if len(src) > 65536 {
		const sz, pool = 589824, 0
		tmp, ok := encBetterPools[pool].Get().(*[sz]byte)
		if !ok {
			tmp = &[sz]byte{}
		}
		race.WriteSlice(tmp[:])
		defer encBetterPools[pool].Put(tmp)
		return encodeSnappyBetterBlockAsm(dst, src, tmp)
	}

	if len(src) >= limit12B {
		const sz, pool = 294912, 4
		tmp, ok := encBetterPools[pool].Get().(*[sz]byte)
		if !ok {
			tmp = &[sz]byte{}
		}
		race.WriteSlice(tmp[:])
		defer encBetterPools[pool].Put(tmp)

		return encodeSnappyBetterBlockAsm64K(dst, src, tmp)
	}
	if len(src) >= limit10B {
		const sz, pool = 81920, 0
		tmp, ok := encBetterPools[pool].Get().(*[sz]byte)
		if !ok {
			tmp = &[sz]byte{}
		}
		race.WriteSlice(tmp[:])
		defer encBetterPools[pool].Put(tmp)

		return encodeSnappyBetterBlockAsm12B(dst, src, tmp)
	}
	if len(src) >= limit8B {
		const sz, pool = 20480, 1
		tmp, ok := encBetterPools[pool].Get().(*[sz]byte)
		if !ok {
			tmp = &[sz]byte{}
		}
		race.WriteSlice(tmp[:])
		defer encBetterPools[pool].Put(tmp)
		return encodeSnappyBetterBlockAsm10B(dst, src, tmp)
	}
	if len(src) < minNonLiteralBlockSize {
		return 0
	}

	const sz, pool = 5120, 2
	tmp, ok := encBetterPools[pool].Get().(*[sz]byte)
	if !ok {
		tmp = &[sz]byte{}
	}
	race.WriteSlice(tmp[:])
	defer encBetterPools[pool].Put(tmp)
	return encodeSnappyBetterBlockAsm8B(dst, src, tmp)
}
