/**
 * Reed-Solomon Coding over 8-bit values.
 *
 * Copyright 2015, Klaus Post
 * Copyright 2015, Backblaze, Inc.
 */

// Package reedsolomon enables Erasure Coding in Go
//
// For usage and examples, see https://github.com/klauspost/reedsolomon
package reedsolomon

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"runtime"
	"sync"

	"github.com/klauspost/cpuid/v2"
)

// Encoder is an interface to encode Reed-Salomon parity sets for your data.
type Encoder interface {
	// Encode parity for a set of data shards.
	// Input is 'shards' containing data shards followed by parity shards.
	// The number of shards must match the number given to New().
	// Each shard is a byte array, and they must all be the same size.
	// The parity shards will always be overwritten and the data shards
	// will remain the same, so it is safe for you to read from the
	// data shards while this is running.
	Encode(shards [][]byte) error

	// EncodeIdx will add parity for a single data shard.
	// Parity shards should start out as 0. The caller must zero them.
	// Data shards must be delivered exactly once. There is no check for this.
	// The parity shards will always be updated and the data shards will remain the same.
	EncodeIdx(dataShard []byte, idx int, parity [][]byte) error

	// Verify returns true if the parity shards contain correct data.
	// The data is the same format as Encode. No data is modified, so
	// you are allowed to read from data while this is running.
	Verify(shards [][]byte) (bool, error)

	// Reconstruct will recreate the missing shards if possible.
	//
	// Given a list of shards, some of which contain data, fills in the
	// ones that don't have data.
	//
	// The length of the array must be equal to the total number of shards.
	// You indicate that a shard is missing by setting it to nil or zero-length.
	// If a shard is zero-length but has sufficient capacity, that memory will
	// be used, otherwise a new []byte will be allocated.
	//
	// If there are too few shards to reconstruct the missing
	// ones, ErrTooFewShards will be returned.
	//
	// The reconstructed shard set is complete, but integrity is not verified.
	// Use the Verify function to check if data set is ok.
	Reconstruct(shards [][]byte) error

	// ReconstructData will recreate any missing data shards, if possible.
	//
	// Given a list of shards, some of which contain data, fills in the
	// data shards that don't have data.
	//
	// The length of the array must be equal to Shards.
	// You indicate that a shard is missing by setting it to nil or zero-length.
	// If a shard is zero-length but has sufficient capacity, that memory will
	// be used, otherwise a new []byte will be allocated.
	//
	// If there are too few shards to reconstruct the missing
	// ones, ErrTooFewShards will be returned.
	//
	// As the reconstructed shard set may contain missing parity shards,
	// calling the Verify function is likely to fail.
	ReconstructData(shards [][]byte) error

	// ReconstructSome will recreate only requested shards, if possible.
	//
	// Given a list of shards, some of which contain data, fills in the
	// shards indicated by true values in the "required" parameter.
	// The length of the "required" array must be equal to either Shards or DataShards.
	// If the length is equal to DataShards, the reconstruction of parity shards will be ignored.
	//
	// The length of "shards" array must be equal to Shards.
	// You indicate that a shard is missing by setting it to nil or zero-length.
	// If a shard is zero-length but has sufficient capacity, that memory will
	// be used, otherwise a new []byte will be allocated.
	//
	// If there are too few shards to reconstruct the missing
	// ones, ErrTooFewShards will be returned.
	//
	// As the reconstructed shard set may contain missing parity shards,
	// calling the Verify function is likely to fail.
	ReconstructSome(shards [][]byte, required []bool) error

	// Update parity is use for change a few data shards and update it's parity.
	// Input 'newDatashards' containing data shards changed.
	// Input 'shards' containing old data shards (if data shard not changed, it can be nil) and old parity shards.
	// New parity shards will in shards[DataShards:]
	// Update is very useful if DataShards much larger than ParityShards and changed data shards is few.
	// It will be faster than Encode and will not need read all data shards to encode.
	// Note that the data shards in shards will *not* be updated.
	Update(shards [][]byte, newDatashards [][]byte) error

	// Split a data slice into the number of shards given to the encoder,
	// and create empty parity shards if necessary.
	//
	// The data will be split into equally sized shards.
	// If the data size isn't divisible by the number of shards,
	// the last shard will contain extra zeros.
	//
	// If there is extra capacity on the provided data slice
	// it will be used instead of allocating parity shards.
	// It will be zeroed out.
	//
	// There must be at least 1 byte otherwise ErrShortData will be
	// returned.
	//
	// The data will not be copied, except for the last shard, so you
	// should not modify the data of the input slice afterwards.
	Split(data []byte) ([][]byte, error)

	// Join the shards and write the data segment to dst.
	//
	// Only the data shards are considered.
	// You must supply the exact output size you want.
	// If there are to few shards given, ErrTooFewShards will be returned.
	// If the total data size is less than outSize, ErrShortData will be returned.
	Join(dst io.Writer, shards [][]byte, outSize int) error
}

// Extensions is an optional interface.
// All returned instances will support this interface.
type Extensions interface {
	// ShardSizeMultiple will return the size the shard sizes must be a multiple of.
	ShardSizeMultiple() int

	// DataShards will return the number of data shards.
	DataShards() int

	// ParityShards will return the number of parity shards.
	ParityShards() int

	// TotalShards will return the total number of shards.
	TotalShards() int

	// AllocAligned will allocate TotalShards number of slices,
	// aligned to reasonable memory sizes.
	// Provide the size of each shard.
	AllocAligned(each int) [][]byte

	// DecodeIdx allows progressively decoding shards.
	// dst slices that are non-nil will be filled with decoded data.
	// On first call, dst non-nil slices should be zero.
	// expectInput indicates which input shards are expected (true = shard expected).
	// input provides the shards - non-nil slices are treated as provided data.
	// len(dst) must equal len(input) must equal TotalShards.
	// If expectInput == nil (merge mode): input shards are XORed into corresponding dst shards.
	// This allows merging of partial decodings from different sources.
	// Not all implementations supports this. ErrNotSupported will be returned if not supported.
	DecodeIdx(dst [][]byte, expectInput []bool, input [][]byte) error
}

const (
	codeGenMinSize           = 64
	codeGenMinShards         = 3
	gfniCodeGenMaxGoroutines = 4

	intSize = 32 << (^uint(0) >> 63) // 32 or 64
	maxInt  = 1<<(intSize-1) - 1
)

// reedSolomon contains a matrix for a specific
// distribution of datashards and parity shards.
// Construct if using New()
type reedSolomon struct {
	dataShards   int // Number of data shards, should not be modified.
	parityShards int // Number of parity shards, should not be modified.
	totalShards  int // Total number of shards. Calculated, and should not be modified.
	m            matrix
	tree         *inversionTree
	parity       [][]byte
	o            options
	mPoolSz      int
	mPool        sync.Pool // Pool for temp matrices, etc
}

var _ = Extensions(&reedSolomon{})

func (r *reedSolomon) ShardSizeMultiple() int {
	return 1
}

func (r *reedSolomon) DataShards() int {
	return r.dataShards
}

func (r *reedSolomon) ParityShards() int {
	return r.parityShards
}

func (r *reedSolomon) TotalShards() int {
	return r.totalShards
}

func (r *reedSolomon) AllocAligned(each int) [][]byte {
	return AllocAligned(r.totalShards, each)
}

// ErrInvShardNum will be returned by New, if you attempt to create
// an Encoder with less than one data shard or less than zero parity
// shards.
var ErrInvShardNum = errors.New("cannot create Encoder with less than one data shard or less than zero parity shards")

// ErrMaxShardNum will be returned by New, if you attempt to create an
// Encoder where data and parity shards are bigger than the order of
// GF(2^8).
var ErrMaxShardNum = errors.New("cannot create Encoder with more than 256 data+parity shards")

// ErrNotSupported is returned when an operation is not supported.
var ErrNotSupported = errors.New("operation not supported")

// buildMatrix creates the matrix to use for encoding, given the
// number of data shards and the number of total shards.
//
// The top square of the matrix is guaranteed to be an identity
// matrix, which means that the data shards are unchanged after
// encoding.
func buildMatrix(dataShards, totalShards int) (matrix, error) {
	// Start with a Vandermonde matrix.  This matrix would work,
	// in theory, but doesn't have the property that the data
	// shards are unchanged after encoding.
	vm, err := vandermonde(totalShards, dataShards)
	if err != nil {
		return nil, err
	}

	// Multiply by the inverse of the top square of the matrix.
	// This will make the top square be the identity matrix, but
	// preserve the property that any square subset of rows is
	// invertible.
	top, err := vm.SubMatrix(0, 0, dataShards, dataShards)
	if err != nil {
		return nil, err
	}

	topInv, err := top.Invert()
	if err != nil {
		return nil, err
	}

	return vm.Multiply(topInv)
}

// buildMatrixJerasure creates the same encoding matrix as Jerasure library
//
// The top square of the matrix is guaranteed to be an identity
// matrix, which means that the data shards are unchanged after
// encoding.
func buildMatrixJerasure(dataShards, totalShards int) (matrix, error) {
	// Start with a Vandermonde matrix.  This matrix would work,
	// in theory, but doesn't have the property that the data
	// shards are unchanged after encoding.
	vm, err := vandermonde(totalShards, dataShards)
	if err != nil {
		return nil, err
	}

	// Jerasure does this:
	// first row is always 100..00
	vm[0][0] = 1
	for i := 1; i < dataShards; i++ {
		vm[0][i] = 0
	}
	// last row is always 000..01
	for i := 0; i < dataShards-1; i++ {
		vm[totalShards-1][i] = 0
	}
	vm[totalShards-1][dataShards-1] = 1

	for i := range dataShards {
		// Find the row where i'th col is not 0
		r := i
		for ; r < totalShards && vm[r][i] == 0; r++ {
		}
		if r != i {
			// Swap it with i'th row if not already
			t := vm[r]
			vm[r] = vm[i]
			vm[i] = t
		}
		// Multiply by the inverted matrix (same as vm.Multiply(vm[0:dataShards].Invert()))
		if vm[i][i] != 1 {
			// Make vm[i][i] = 1 by dividing the column by vm[i][i]
			tmp := galOneOver(vm[i][i])
			for j := range totalShards {
				vm[j][i] = galMultiply(vm[j][i], tmp)
			}
		}
		for j := range dataShards {
			// Make vm[i][j] = 0 where j != i by adding vm[i][j]*vm[.][i] to each column
			tmp := vm[i][j]
			if j != i && tmp != 0 {
				for r := range totalShards {
					vm[r][j] = galAdd(vm[r][j], galMultiply(tmp, vm[r][i]))
				}
			}
		}
	}

	// Make vm[dataShards] row all ones - divide each column j by vm[dataShards][j]
	for j := range dataShards {
		tmp := vm[dataShards][j]
		if tmp != 1 {
			tmp = galOneOver(tmp)
			for i := dataShards; i < totalShards; i++ {
				vm[i][j] = galMultiply(vm[i][j], tmp)
			}
		}
	}

	// Make vm[dataShards...totalShards-1][0] column all ones - divide each row
	for i := dataShards + 1; i < totalShards; i++ {
		tmp := vm[i][0]
		if tmp != 1 {
			tmp = galOneOver(tmp)
			for j := range dataShards {
				vm[i][j] = galMultiply(vm[i][j], tmp)
			}
		}
	}

	return vm, nil
}

// buildMatrixPAR1 creates the matrix to use for encoding according to
// the PARv1 spec, given the number of data shards and the number of
// total shards. Note that the method they use is buggy, and may lead
// to cases where recovery is impossible, even if there are enough
// parity shards.
//
// The top square of the matrix is guaranteed to be an identity
// matrix, which means that the data shards are unchanged after
// encoding.
func buildMatrixPAR1(dataShards, totalShards int) (matrix, error) {
	result, err := newMatrix(totalShards, dataShards)
	if err != nil {
		return nil, err
	}

	for r, row := range result {
		// The top portion of the matrix is the identity
		// matrix, and the bottom is a transposed Vandermonde
		// matrix starting at 1 instead of 0.
		if r < dataShards {
			result[r][r] = 1
		} else {
			for c := range row {
				result[r][c] = galExp(byte(c+1), r-dataShards)
			}
		}
	}
	return result, nil
}

func buildMatrixCauchy(dataShards, totalShards int) (matrix, error) {
	result, err := newMatrix(totalShards, dataShards)
	if err != nil {
		return nil, err
	}

	for r, row := range result {
		// The top portion of the matrix is the identity
		// matrix, and the bottom is a transposed Cauchy matrix.
		if r < dataShards {
			result[r][r] = 1
		} else {
			for c := range row {
				result[r][c] = invTable[(byte(r ^ c))]
			}
		}
	}
	return result, nil
}

// buildXorMatrix can be used to build a matrix with pure XOR
// operations if there is only one parity shard.
func buildXorMatrix(dataShards, totalShards int) (matrix, error) {
	if dataShards+1 != totalShards {
		return nil, errors.New("internal error")
	}
	result, err := newMatrix(totalShards, dataShards)
	if err != nil {
		return nil, err
	}

	for r, row := range result {
		// The top portion of the matrix is the identity
		// matrix.
		if r < dataShards {
			result[r][r] = 1
		} else {
			// Set all values to 1 (XOR)
			for c := range row {
				result[r][c] = 1
			}
		}
	}
	return result, nil
}

// New creates a new encoder and initializes it to
// the number of data shards and parity shards that
// you want to use. You can reuse this encoder.
// Note that the maximum number of total shards is 65536, with some
// restrictions for a total larger than 256:
//
//   - Shard sizes must be multiple of 64
//   - The methods Join/Split/Update/EncodeIdx are not supported
//
// If no options are supplied, default options are used.
func New(dataShards, parityShards int, opts ...Option) (Encoder, error) {
	o := defaultOptions
	for _, opt := range opts {
		opt(&o)
	}

	totShards := dataShards + parityShards
	switch {
	case o.withLeopard == leopardGF16 && parityShards > 0 || totShards > 256:
		return newFF16(dataShards, parityShards, o)
	case o.withLeopard == leopardAlways && parityShards > 0:
		return newFF8(dataShards, parityShards, o)
	}
	if totShards > 256 {
		return nil, ErrMaxShardNum
	}

	r := reedSolomon{
		dataShards:   dataShards,
		parityShards: parityShards,
		totalShards:  dataShards + parityShards,
		o:            o,
	}

	if dataShards <= 0 || parityShards < 0 {
		return nil, ErrInvShardNum
	}

	if parityShards == 0 {
		return &r, nil
	}

	var err error
	switch {
	case r.o.customMatrix != nil:
		if len(r.o.customMatrix) < parityShards {
			return nil, errors.New("coding matrix must contain at least parityShards rows")
		}
		r.m = make([][]byte, r.totalShards)
		for i := range dataShards {
			r.m[i] = make([]byte, dataShards)
			r.m[i][i] = 1
		}
		for k, row := range r.o.customMatrix {
			if len(row) < dataShards {
				return nil, errors.New("coding matrix must contain at least dataShards columns")
			}
			r.m[dataShards+k] = make([]byte, dataShards)
			copy(r.m[dataShards+k], row)
		}
	case r.o.fastOneParity && parityShards == 1:
		r.m, err = buildXorMatrix(dataShards, r.totalShards)
	case r.o.useCauchy:
		r.m, err = buildMatrixCauchy(dataShards, r.totalShards)
	case r.o.usePAR1Matrix:
		r.m, err = buildMatrixPAR1(dataShards, r.totalShards)
	case r.o.useJerasureMatrix:
		r.m, err = buildMatrixJerasure(dataShards, r.totalShards)
	default:
		r.m, err = buildMatrix(dataShards, r.totalShards)
	}
	if err != nil {
		return nil, err
	}

	// Calculate what we want per round
	r.o.perRound = max(cpuid.CPU.Cache.L2, 128<<10)

	_, _, useCodeGen := r.hasCodeGen(codeGenMinSize, codeGenMaxInputs, codeGenMaxOutputs)

	divide := parityShards + 1
	if codeGen && useCodeGen && (dataShards > codeGenMaxInputs || parityShards > codeGenMaxOutputs) {
		// Base on L1 cache if we have many inputs.
		r.o.perRound = max(cpuid.CPU.Cache.L1D, 32<<10)
		divide = 0
		if dataShards > codeGenMaxInputs {
			divide += codeGenMaxInputs
		} else {
			divide += dataShards
		}
		if parityShards > codeGenMaxInputs {
			divide += codeGenMaxOutputs
		} else {
			divide += parityShards
		}
	}

	if cpuid.CPU.ThreadsPerCore > 1 && r.o.maxGoroutines > cpuid.CPU.PhysicalCores {
		// If multiple threads per core, make sure they don't contend for cache.
		r.o.perRound /= cpuid.CPU.ThreadsPerCore
	}

	// 1 input + parity must fit in cache, and we add one more to be safer.
	r.o.perRound = r.o.perRound / divide
	// Align to 64 bytes.
	r.o.perRound = max(
		// Final sanity check...
		((r.o.perRound+63)/64)*64, 1<<10)

	if r.o.minSplitSize <= 0 {
		// Set minsplit as high as we can, but still have parity in L1.
		cacheSize := cpuid.CPU.Cache.L1D
		if cacheSize <= 0 {
			cacheSize = 32 << 10
		}

		r.o.minSplitSize = max(
			// Min 1K
			cacheSize/(parityShards+1), 1024)
	}

	if r.o.shardSize > 0 {
		p := runtime.GOMAXPROCS(0)
		if p == 1 || r.o.shardSize <= r.o.minSplitSize*2 {
			// Not worth it.
			r.o.maxGoroutines = 1
		} else {
			g := r.o.shardSize / r.o.perRound

			// Overprovision by a factor of 2.
			if g < p*2 && r.o.perRound > r.o.minSplitSize*2 {
				g = p * 2
				r.o.perRound /= 2
			}

			// Have g be multiple of p
			g += p - 1
			g -= g % p

			r.o.maxGoroutines = g
		}
	}

	// Generated AVX2 does not need data to stay in L1 cache between runs.
	// We will be purely limited by RAM speed.
	if useCodeGen && r.o.maxGoroutines > codeGenMaxGoroutines {
		r.o.maxGoroutines = codeGenMaxGoroutines
	}

	if _, _, useGFNI := r.canGFNI(codeGenMinSize, codeGenMaxInputs, codeGenMaxOutputs); useGFNI && r.o.maxGoroutines > gfniCodeGenMaxGoroutines {
		r.o.maxGoroutines = gfniCodeGenMaxGoroutines
	}

	// Skip 2 byte table method if we have many shards.
	// This is the approximate switchover for Zen5.
	r.o.skip2B = dataShards+parityShards >= 20

	// Inverted matrices are cached in a tree keyed by the indices
	// of the invalid rows of the data to reconstruct.
	// The inversion root node will have the identity matrix as
	// its inversion matrix because it implies there are no errors
	// with the original data.
	if r.o.inversionCache {
		r.tree = newInversionTree(dataShards, parityShards)
	}

	r.parity = make([][]byte, parityShards)
	for i := range r.parity {
		r.parity[i] = r.m[dataShards+i]
	}

	if codeGen /* && r.o.useAVX2 */ {
		paddedDS := r.dataShards
		if codeGenPadInputs > 1 {
			paddedDS = ((paddedDS + codeGenPadInputs - 1) / codeGenPadInputs) * codeGenPadInputs
		}
		sz := paddedDS * r.parityShards * 2 * 32
		r.mPool.New = func() any {
			return AllocAligned(1, sz)[0]
		}
		r.mPoolSz = sz
	}
	return &r, err
}

func (r *reedSolomon) getTmpSlice() []byte {
	return r.mPool.Get().([]byte)
}

func (r *reedSolomon) putTmpSlice(b []byte) {
	if b != nil && cap(b) >= r.mPoolSz {
		r.mPool.Put(b[:r.mPoolSz])
		return
	}
	if false {
		// Sanity check
		panic(fmt.Sprintf("got short tmp returned, want %d, got %d", r.mPoolSz, cap(b)))
	}
}

// multiplyRowWithMatrix multiplies a single row with a matrix to produce a new row.
// result[j] = sum over i of row[i] * matrix[i][j]
func multiplyRowWithMatrix(row []byte, matrix [][]byte) []byte {
	if len(row) != len(matrix) {
		panic("row length must match matrix height")
	}
	result := make([]byte, len(matrix[0]))
	for j := range result {
		for i := range row {
			result[j] ^= galMultiply(row[i], matrix[i][j])
		}
	}
	return result
}

// getDecodeMatrix returns the inverted matrix for decoding, using cached version if available
func (r *reedSolomon) getDecodeMatrix(validIndices, invalidIndices []int) ([][]byte, error) {
	// Attempt to get the cached inverted matrix out of the tree
	// based on the indices of the invalid rows.
	dataDecodeMatrix := r.tree.GetInvertedMatrix(invalidIndices)

	// If the inverted matrix isn't cached in the tree yet we must
	// construct it ourselves and insert it into the tree for the
	// future.  In this way the inversion tree is lazily loaded.
	if dataDecodeMatrix == nil {
		// Pull out the rows of the matrix that correspond to the
		// shards that we have and build a square matrix.  This
		// matrix could be used to generate the shards that we have
		// from the original data.
		subMatrix, _ := newMatrix(r.dataShards, r.dataShards)
		for subMatrixRow, validIndex := range validIndices[:r.dataShards] {
			for c := 0; c < r.dataShards; c++ {
				subMatrix[subMatrixRow][c] = r.m[validIndex][c]
			}
		}
		// Invert the matrix, so we can go from the encoded shards
		// back to the original data.  Then pull out the row that
		// generates the shard that we want to decode.  Note that
		// since this matrix maps back to the original data, it can
		// be used to create a data shard, but not a parity shard.
		var err error
		dataDecodeMatrix, err = subMatrix.Invert()
		if err != nil {
			return nil, err
		}

		// Cache the inverted matrix in the tree for future use keyed on the
		// indices of the invalid rows.
		err = r.tree.InsertInvertedMatrix(invalidIndices, dataDecodeMatrix, r.totalShards)
		if err != nil {
			return nil, err
		}
	}

	return dataDecodeMatrix, nil
}

// ErrTooFewShards is returned if too few shards where given to
// Encode/Verify/Reconstruct/Update. It will also be returned from Reconstruct
// if there were too few shards to reconstruct the missing data.
var ErrTooFewShards = errors.New("too few shards given")

// Encode parity for a set of data shards.
// An array 'shards' containing data shards followed by parity shards.
// The number of shards must match the number given to New.
// Each shard is a byte array, and they must all be the same size.
// The parity shards will always be overwritten and the data shards
// will remain the same.
func (r *reedSolomon) Encode(shards [][]byte) error {
	if len(shards) != r.totalShards {
		return ErrTooFewShards
	}

	err := checkShards(shards, false)
	if err != nil {
		return err
	}

	// Get the slice of output buffers.
	output := shards[r.dataShards:]

	// Do the coding.
	r.codeSomeShards(r.parity, shards[0:r.dataShards], output[:r.parityShards], len(shards[0]), true)
	return nil
}

// EncodeIdx will add parity for a single data shard.
// Parity shards should start out zeroed. The caller must zero them before first call.
// Data shards should only be delivered once. There is no check for this.
// The parity shards will always be updated and the data shards will remain the unchanged.
func (r *reedSolomon) EncodeIdx(dataShard []byte, idx int, parity [][]byte) error {
	if len(parity) != r.parityShards {
		return ErrTooFewShards
	}
	if len(parity) == 0 {
		return nil
	}
	if idx < 0 || idx >= r.dataShards {
		return ErrInvShardNum
	}
	err := checkShards(parity, false)
	if err != nil {
		return err
	}
	if len(parity[0]) != len(dataShard) {
		return ErrShardSize
	}

	if codeGen && len(dataShard) >= r.o.perRound && len(parity) >= codeGenMinShards && (pshufb || r.o.useAvx512GFNI || r.o.useAvxGNFI) {
		m := make([][]byte, r.parityShards)
		for iRow := range m {
			m[iRow] = r.parity[iRow][idx : idx+1]
		}
		if r.o.useAvx512GFNI || r.o.useAvxGNFI {
			r.codeSomeShardsGFNI(m, [][]byte{dataShard}, parity, len(dataShard), false, nil, nil)
		} else {
			r.codeSomeShardsAVXP(m, [][]byte{dataShard}, parity, len(dataShard), false, nil, nil)
		}
		return nil
	}

	// Process using no goroutines for now.
	start, end := 0, r.o.perRound
	if end > len(dataShard) {
		end = len(dataShard)
	}

	for start < len(dataShard) {
		in := dataShard[start:end]
		for iRow := 0; iRow < r.parityShards; iRow++ {
			galMulSliceXor(r.parity[iRow][idx], in, parity[iRow][start:end], &r.o)
		}
		start = end
		end += r.o.perRound
		if end > len(dataShard) {
			end = len(dataShard)
		}
	}
	return nil
}

// ErrInvalidInput is returned if invalid input parameter of Update.
var ErrInvalidInput = errors.New("invalid input")

func (r *reedSolomon) Update(shards [][]byte, newDatashards [][]byte) error {
	if len(shards) != r.totalShards {
		return ErrTooFewShards
	}

	if len(newDatashards) != r.dataShards {
		return ErrTooFewShards
	}

	err := checkShards(shards, true)
	if err != nil {
		return err
	}

	err = checkShards(newDatashards, true)
	if err != nil {
		return err
	}

	for i := range newDatashards {
		if newDatashards[i] != nil && shards[i] == nil {
			return ErrInvalidInput
		}
	}
	for _, p := range shards[r.dataShards:] {
		if p == nil {
			return ErrInvalidInput
		}
	}

	shardSize := shardSize(shards)

	// Get the slice of output buffers.
	output := shards[r.dataShards:]

	// Do the coding.
	r.updateParityShards(r.parity, shards[0:r.dataShards], newDatashards[0:r.dataShards], output, r.parityShards, shardSize)
	return nil
}

func (r *reedSolomon) updateParityShards(matrixRows, oldinputs, newinputs, outputs [][]byte, outputCount, byteCount int) {
	if len(outputs) == 0 {
		return
	}

	if r.o.maxGoroutines > 1 && byteCount > r.o.minSplitSize {
		r.updateParityShardsP(matrixRows, oldinputs, newinputs, outputs, outputCount, byteCount)
		return
	}

	for c := 0; c < r.dataShards; c++ {
		in := newinputs[c]
		if in == nil {
			continue
		}
		oldin := oldinputs[c]
		// oldinputs data will be changed
		sliceXor(in, oldin, &r.o)
		for iRow := range outputCount {
			galMulSliceXor(matrixRows[iRow][c], oldin, outputs[iRow], &r.o)
		}
	}
}

func (r *reedSolomon) updateParityShardsP(matrixRows, oldinputs, newinputs, outputs [][]byte, outputCount, byteCount int) {
	var wg sync.WaitGroup
	do := max(byteCount/r.o.maxGoroutines, r.o.minSplitSize)
	start := 0
	for start < byteCount {
		if start+do > byteCount {
			do = byteCount - start
		}
		wg.Add(1)
		go func(start, stop int) {
			for c := 0; c < r.dataShards; c++ {
				in := newinputs[c]
				if in == nil {
					continue
				}
				oldin := oldinputs[c]
				// oldinputs data will be change
				sliceXor(in[start:stop], oldin[start:stop], &r.o)
				for iRow := range outputCount {
					galMulSliceXor(matrixRows[iRow][c], oldin[start:stop], outputs[iRow][start:stop], &r.o)
				}
			}
			wg.Done()
		}(start, start+do)
		start += do
	}
	wg.Wait()
}

// Verify returns true if the parity shards contain the right data.
// The data is the same format as Encode. No data is modified.
func (r *reedSolomon) Verify(shards [][]byte) (bool, error) {
	if len(shards) != r.totalShards {
		return false, ErrTooFewShards
	}
	err := checkShards(shards, false)
	if err != nil {
		return false, err
	}

	// Slice of buffers being checked.
	toCheck := shards[r.dataShards:]

	// Do the checking.
	return r.checkSomeShards(r.parity, shards[:r.dataShards], toCheck[:r.parityShards], len(shards[0])), nil
}

// Multiplies a subset of rows from a coding matrix by a full set of
// input totalShards to produce some output totalShards.
// 'matrixRows' is The rows from the matrix to use.
// 'inputs' An array of byte arrays, each of which is one input shard.
// The number of inputs used is determined by the length of each matrix row.
// outputs Byte arrays where the computed totalShards are stored.
// The number of outputs computed, and the
// number of matrix rows used, is determined by
// outputCount, which is the number of outputs to compute.
func (r *reedSolomon) codeSomeShards(matrixRows, inputs, outputs [][]byte, byteCount int, clear bool) {
	if len(outputs) == 0 {
		return
	}
	if r.o.maxGoroutines > 1 && byteCount > r.o.minSplitSize {
		r.codeSomeShardsP(matrixRows, inputs, outputs, byteCount, clear)
		return
	}

	// Process using no goroutines
	start, end := 0, r.o.perRound
	if end > len(inputs[0]) {
		end = len(inputs[0])
	}
	if galMulGFNI, galMulGFNIXor, useGFNI := r.canGFNI(byteCount, len(inputs), len(outputs)); useGFNI {
		var gfni [codeGenMaxInputs * codeGenMaxOutputs]uint64
		m := genGFNIMatrix(matrixRows, len(inputs), 0, len(outputs), gfni[:])
		if clear {
			start += (*galMulGFNI)(m, inputs, outputs, 0, byteCount)
		} else {
			start += (*galMulGFNIXor)(m, inputs, outputs, 0, byteCount)
		}
		end = len(inputs[0])
	} else if galMulGen, galMulGenXor, ok := r.hasCodeGen(byteCount, len(inputs), len(outputs)); ok {
		m := genCodeGenMatrix(matrixRows, len(inputs), 0, len(outputs), r.o.vectorLength, r.getTmpSlice())
		if clear {
			start += (*galMulGen)(m, inputs, outputs, 0, byteCount)
		} else {
			start += (*galMulGenXor)(m, inputs, outputs, 0, byteCount)
		}
		r.putTmpSlice(m)
		end = len(inputs[0])
	} else if galMulGen, galMulGenXor, ok := r.hasCodeGen(byteCount, codeGenMaxInputs, codeGenMaxOutputs); len(inputs)+len(outputs) > codeGenMinShards && ok {
		var gfni [codeGenMaxInputs * codeGenMaxOutputs]uint64
		end = len(inputs[0])
		inIdx := 0
		m := r.getTmpSlice()
		defer r.putTmpSlice(m)
		ins := inputs
		for len(ins) > 0 {
			inPer := ins
			if len(inPer) > codeGenMaxInputs {
				inPer = inPer[:codeGenMaxInputs]
			}
			outs := outputs
			outIdx := 0
			for len(outs) > 0 {
				outPer := outs
				if len(outPer) > codeGenMaxOutputs {
					outPer = outPer[:codeGenMaxOutputs]
				}
				if useGFNI {
					m := genGFNIMatrix(matrixRows[outIdx:], len(inPer), inIdx, len(outPer), gfni[:])
					if inIdx == 0 && clear {
						start = (*galMulGFNI)(m, inPer, outPer, 0, byteCount)
					} else {
						start = (*galMulGFNIXor)(m, inPer, outPer, 0, byteCount)
					}
				} else {
					m = genCodeGenMatrix(matrixRows[outIdx:], len(inPer), inIdx, len(outPer), r.o.vectorLength, m)
					if inIdx == 0 && clear {
						start = (*galMulGen)(m, inPer, outPer, 0, byteCount)
					} else {
						start = (*galMulGenXor)(m, inPer, outPer, 0, byteCount)
					}
				}
				outIdx += len(outPer)
				outs = outs[len(outPer):]
			}
			inIdx += len(inPer)
			ins = ins[len(inPer):]
		}
		if start >= end {
			return
		}
	}
	for start < len(inputs[0]) {
		for c := range inputs {
			in := inputs[c][start:end]
			for iRow := range outputs {
				if c == 0 && clear {
					galMulSlice(matrixRows[iRow][c], in, outputs[iRow][start:end], &r.o)
				} else {
					galMulSliceXor(matrixRows[iRow][c], in, outputs[iRow][start:end], &r.o)
				}
			}
		}
		start = end
		end += r.o.perRound
		if end > len(inputs[0]) {
			end = len(inputs[0])
		}
	}
}

// Perform the same as codeSomeShards, but split the workload into
// several goroutines.
func (r *reedSolomon) codeSomeShardsP(matrixRows, inputs, outputs [][]byte, byteCount int, clear bool) {
	var wg sync.WaitGroup
	gor := r.o.maxGoroutines

	var genMatrix []byte
	var gfniMatrix []uint64
	galMulGen, galMulGenXor, useCodeGen := r.hasCodeGen(byteCount, len(inputs), len(outputs))
	galMulGFNI, galMulGFNIXor, useGFNI := r.canGFNI(byteCount, len(inputs), len(outputs))
	if useGFNI {
		var tmp [codeGenMaxInputs * codeGenMaxOutputs]uint64
		gfniMatrix = genGFNIMatrix(matrixRows, len(inputs), 0, len(outputs), tmp[:])
	} else if useCodeGen {
		genMatrix = genCodeGenMatrix(matrixRows, len(inputs), 0, len(outputs), r.o.vectorLength, r.getTmpSlice())
		defer r.putTmpSlice(genMatrix)
	} else if galMulGFNI, galMulGFNIXor, useGFNI := r.canGFNI(byteCount/4, codeGenMaxInputs, codeGenMaxOutputs); useGFNI &&
		len(inputs)+len(outputs) > codeGenMinShards {
		// It appears there is a switchover point at around 10MB where
		// Regular processing is faster...
		r.codeSomeShardsGFNI(matrixRows, inputs, outputs, byteCount, clear, galMulGFNI, galMulGFNIXor)
		return
	} else if galMulGen, galMulGenXor, ok := r.hasCodeGen(byteCount/4, codeGenMaxInputs, codeGenMaxOutputs); ok &&
		len(inputs)+len(outputs) > codeGenMinShards {
		// It appears there is a switchover point at around 10MB where
		// Regular processing is faster...
		r.codeSomeShardsAVXP(matrixRows, inputs, outputs, byteCount, clear, galMulGen, galMulGenXor)
		return
	}

	do := max(byteCount/gor, r.o.minSplitSize)

	exec := func(start, stop int) {
		if stop-start >= 64 {
			if useGFNI {
				if clear {
					start += (*galMulGFNI)(gfniMatrix, inputs, outputs, start, stop)
				} else {
					start += (*galMulGFNIXor)(gfniMatrix, inputs, outputs, start, stop)
				}
			} else if useCodeGen {
				if clear {
					start += (*galMulGen)(genMatrix, inputs, outputs, start, stop)
				} else {
					start += (*galMulGenXor)(genMatrix, inputs, outputs, start, stop)
				}
			}
		}

		lstart, lstop := start, start+r.o.perRound
		if lstop > stop {
			lstop = stop
		}
		for lstart < stop {
			for c := range inputs {
				in := inputs[c][lstart:lstop]
				for iRow := range outputs {
					if c == 0 && clear {
						galMulSlice(matrixRows[iRow][c], in, outputs[iRow][lstart:lstop], &r.o)
					} else {
						galMulSliceXor(matrixRows[iRow][c], in, outputs[iRow][lstart:lstop], &r.o)
					}
				}
			}
			lstart = lstop
			lstop += r.o.perRound
			if lstop > stop {
				lstop = stop
			}
		}
		wg.Done()
	}
	if gor <= 1 {
		wg.Add(1)
		exec(0, byteCount)
		return
	}

	// Make sizes divisible by 64
	do = (do + 63) & (^63)
	start := 0
	for start < byteCount {
		if start+do > byteCount {
			do = byteCount - start
		}

		wg.Add(1)
		go exec(start, start+do)
		start += do
	}
	wg.Wait()
}

// Perform the same as codeSomeShards, but split the workload into
// several goroutines.
// If clear is set, the first write will overwrite the output.
func (r *reedSolomon) codeSomeShardsAVXP(matrixRows, inputs, outputs [][]byte, byteCount int, clear bool, galMulGen, galMulGenXor *func(matrix []byte, in [][]byte, out [][]byte, start int, stop int) int) {
	var wg sync.WaitGroup
	gor := r.o.maxGoroutines

	type state struct {
		input  [][]byte
		output [][]byte
		m      []byte
		first  bool
	}
	// Make a plan...
	plan := make([]state, 0, ((len(inputs)+codeGenMaxInputs-1)/codeGenMaxInputs)*((len(outputs)+codeGenMaxOutputs-1)/codeGenMaxOutputs))

	tmp := r.getTmpSlice()
	defer r.putTmpSlice(tmp)

	// Flips between input first to output first.
	// We put the smallest data load in the inner loop.
	if len(inputs) > len(outputs) {
		inIdx := 0
		ins := inputs
		for len(ins) > 0 {
			inPer := ins
			if len(inPer) > codeGenMaxInputs {
				inPer = inPer[:codeGenMaxInputs]
			}
			outs := outputs
			outIdx := 0
			for len(outs) > 0 {
				outPer := outs
				if len(outPer) > codeGenMaxOutputs {
					outPer = outPer[:codeGenMaxOutputs]
				}
				// Generate local matrix
				m := genCodeGenMatrix(matrixRows[outIdx:], len(inPer), inIdx, len(outPer), r.o.vectorLength, tmp)
				tmp = tmp[len(m):]
				plan = append(plan, state{
					input:  inPer,
					output: outPer,
					m:      m,
					first:  inIdx == 0 && clear,
				})
				outIdx += len(outPer)
				outs = outs[len(outPer):]
			}
			inIdx += len(inPer)
			ins = ins[len(inPer):]
		}
	} else {
		outs := outputs
		outIdx := 0
		for len(outs) > 0 {
			outPer := outs
			if len(outPer) > codeGenMaxOutputs {
				outPer = outPer[:codeGenMaxOutputs]
			}

			inIdx := 0
			ins := inputs
			for len(ins) > 0 {
				inPer := ins
				if len(inPer) > codeGenMaxInputs {
					inPer = inPer[:codeGenMaxInputs]
				}
				// Generate local matrix
				m := genCodeGenMatrix(matrixRows[outIdx:], len(inPer), inIdx, len(outPer), r.o.vectorLength, tmp)
				tmp = tmp[len(m):]
				//fmt.Println("bytes:", len(inPer)*r.o.perRound, "out:", len(outPer)*r.o.perRound)
				plan = append(plan, state{
					input:  inPer,
					output: outPer,
					m:      m,
					first:  inIdx == 0 && clear,
				})
				inIdx += len(inPer)
				ins = ins[len(inPer):]
			}
			outIdx += len(outPer)
			outs = outs[len(outPer):]
		}
	}

	do := max(byteCount/gor, r.o.minSplitSize)

	exec := func(start, stop int) {
		defer wg.Done()
		lstart, lstop := start, start+r.o.perRound
		if lstop > stop {
			lstop = stop
		}
		for lstart < stop {
			if galMulGen != nil && galMulGenXor != nil && lstop-lstart >= minCodeGenSize {
				// Execute plan...
				var n int
				for _, p := range plan {
					if p.first {
						n = (*galMulGen)(p.m, p.input, p.output, lstart, lstop)
					} else {
						n = (*galMulGenXor)(p.m, p.input, p.output, lstart, lstop)
					}
				}
				lstart += n
				if lstart == lstop {
					lstop += r.o.perRound
					if lstop > stop {
						lstop = stop
					}
					continue
				}
			}

			for c := range inputs {
				in := inputs[c][lstart:lstop]
				for iRow := range outputs {
					if c == 0 && clear {
						galMulSlice(matrixRows[iRow][c], in, outputs[iRow][lstart:lstop], &r.o)
					} else {
						galMulSliceXor(matrixRows[iRow][c], in, outputs[iRow][lstart:lstop], &r.o)
					}
				}
			}
			lstart = lstop
			lstop += r.o.perRound
			if lstop > stop {
				lstop = stop
			}
		}
	}
	if gor == 1 {
		wg.Add(1)
		exec(0, byteCount)
		return
	}

	// Make sizes divisible by 64
	do = (do + 63) & (^63)
	start := 0
	for start < byteCount {
		if start+do > byteCount {
			do = byteCount - start
		}

		wg.Add(1)
		go exec(start, start+do)
		start += do
	}
	wg.Wait()
}

// Perform the same as codeSomeShards, but split the workload into
// several goroutines.
// If clear is set, the first write will overwrite the output.
func (r *reedSolomon) codeSomeShardsGFNI(matrixRows, inputs, outputs [][]byte, byteCount int, clear bool, galMulGFNI, galMulGFNIXor *func(matrix []uint64, in, out [][]byte, start, stop int) int) {
	var wg sync.WaitGroup
	gor := r.o.maxGoroutines

	type state struct {
		input  [][]byte
		output [][]byte
		m      []uint64
		first  bool
	}
	// Make a plan...
	plan := make([]state, 0, ((len(inputs)+codeGenMaxInputs-1)/codeGenMaxInputs)*((len(outputs)+codeGenMaxOutputs-1)/codeGenMaxOutputs))

	// Flips between input first to output first.
	// We put the smallest data load in the inner loop.
	if len(inputs) > len(outputs) {
		inIdx := 0
		ins := inputs
		for len(ins) > 0 {
			inPer := ins
			if len(inPer) > codeGenMaxInputs {
				inPer = inPer[:codeGenMaxInputs]
			}
			outs := outputs
			outIdx := 0
			for len(outs) > 0 {
				outPer := outs
				if len(outPer) > codeGenMaxOutputs {
					outPer = outPer[:codeGenMaxOutputs]
				}
				// Generate local matrix
				m := genGFNIMatrix(matrixRows[outIdx:], len(inPer), inIdx, len(outPer), make([]uint64, len(inPer)*len(outPer)))
				plan = append(plan, state{
					input:  inPer,
					output: outPer,
					m:      m,
					first:  inIdx == 0 && clear,
				})
				outIdx += len(outPer)
				outs = outs[len(outPer):]
			}
			inIdx += len(inPer)
			ins = ins[len(inPer):]
		}
	} else {
		outs := outputs
		outIdx := 0
		for len(outs) > 0 {
			outPer := outs
			if len(outPer) > codeGenMaxOutputs {
				outPer = outPer[:codeGenMaxOutputs]
			}

			inIdx := 0
			ins := inputs
			for len(ins) > 0 {
				inPer := ins
				if len(inPer) > codeGenMaxInputs {
					inPer = inPer[:codeGenMaxInputs]
				}
				// Generate local matrix
				m := genGFNIMatrix(matrixRows[outIdx:], len(inPer), inIdx, len(outPer), make([]uint64, len(inPer)*len(outPer)))
				//fmt.Println("bytes:", len(inPer)*r.o.perRound, "out:", len(outPer)*r.o.perRound)
				plan = append(plan, state{
					input:  inPer,
					output: outPer,
					m:      m,
					first:  inIdx == 0 && clear,
				})
				inIdx += len(inPer)
				ins = ins[len(inPer):]
			}
			outIdx += len(outPer)
			outs = outs[len(outPer):]
		}
	}

	do := max(byteCount/gor, r.o.minSplitSize)

	exec := func(start, stop int) {
		defer wg.Done()
		lstart, lstop := start, start+r.o.perRound
		if lstop > stop {
			lstop = stop
		}
		for lstart < stop {
			if galMulGFNI != nil && galMulGFNIXor != nil && lstop-lstart >= minCodeGenSize {
				// Execute plan...
				var n int
				for _, p := range plan {
					if p.first {
						n = (*galMulGFNI)(p.m, p.input, p.output, lstart, lstop)
					} else {
						n = (*galMulGFNIXor)(p.m, p.input, p.output, lstart, lstop)
					}
				}
				lstart += n
				if lstart == lstop {
					lstop += r.o.perRound
					if lstop > stop {
						lstop = stop
					}
					continue
				}
			}

			for c := range inputs {
				in := inputs[c][lstart:lstop]
				for iRow := range outputs {
					if c == 0 && clear {
						galMulSlice(matrixRows[iRow][c], in, outputs[iRow][lstart:lstop], &r.o)
					} else {
						galMulSliceXor(matrixRows[iRow][c], in, outputs[iRow][lstart:lstop], &r.o)
					}
				}
			}
			lstart = lstop
			lstop += r.o.perRound
			if lstop > stop {
				lstop = stop
			}
		}
	}

	if gor == 1 {
		wg.Add(1)
		exec(0, byteCount)
		return
	}

	// Make sizes divisible by 64
	do = (do + 63) & (^63)
	start := 0
	for start < byteCount {
		if start+do > byteCount {
			do = byteCount - start
		}

		wg.Add(1)
		go exec(start, start+do)
		start += do
	}
	wg.Wait()
}

// checkSomeShards is mostly the same as codeSomeShards,
// except this will check values and return
// as soon as a difference is found.
func (r *reedSolomon) checkSomeShards(matrixRows, inputs, toCheck [][]byte, byteCount int) bool {
	if len(toCheck) == 0 {
		return true
	}

	outputs := AllocAligned(len(toCheck), byteCount)
	r.codeSomeShards(matrixRows, inputs, outputs, byteCount, true)

	for i, calc := range outputs {
		if !bytes.Equal(calc, toCheck[i]) {
			return false
		}
	}
	return true
}

// ErrShardNoData will be returned if there are no shards,
// or if the length of all shards is zero.
var ErrShardNoData = errors.New("no shard data")

// ErrShardSize is returned if shard length isn't the same for all
// shards.
var ErrShardSize = errors.New("shard sizes do not match")

// ErrInvalidShardSize is returned if shard length doesn't meet the requirements,
// typically a multiple of N.
var ErrInvalidShardSize = errors.New("invalid shard size")

// checkShards will check if shards are the same size
// or 0, if allowed. An error is returned if this fails.
// An error is also returned if all shards are size 0.
func checkShards(shards [][]byte, nilok bool) error {
	size := shardSize(shards)
	if size == 0 {
		return ErrShardNoData
	}
	for _, shard := range shards {
		if len(shard) != size {
			if len(shard) != 0 || !nilok {
				return ErrShardSize
			}
		}
	}
	return nil
}

// shardSize return the size of a single shard.
// The first non-zero size is returned,
// or 0 if all shards are size 0.
func shardSize(shards [][]byte) int {
	for _, shard := range shards {
		if len(shard) != 0 {
			return len(shard)
		}
	}
	return 0
}

// Reconstruct will recreate the missing shards, if possible.
//
// Given a list of shards, some of which contain data, fills in the
// ones that don't have data.
//
// The length of the array must be equal to shards.
// You indicate that a shard is missing by setting it to nil or zero-length.
// If a shard is zero-length but has sufficient capacity, that memory will
// be used, otherwise a new []byte will be allocated.
//
// If there are too few shards to reconstruct the missing
// ones, ErrTooFewShards will be returned.
//
// The reconstructed shard set is complete, but integrity is not verified.
// Use the Verify function to check if data set is ok.
func (r *reedSolomon) Reconstruct(shards [][]byte) error {
	return r.reconstruct(shards, false, nil)
}

// DecodeIdx allows progressively decoding shards into dst.
// Dst slices to be decoded should be filled.
// On the first call, dst slices should be set to all zeros.
// expectInput should be the same for all calls and should
// represent the inputs that are expected to be added.
// The number of expected inputs should at least be the number of data shards.
// Each input should be sent once - and only once.
// len(dst) must be = len(input).
// All sent slices must be the same length.
// The caller must manage all of the above.
// Merging:
// If 'expectInput == nil' no new input is added, but the slices are XORed together.
// This means that the same slices as dst must be set in input.
// This allows merging two separate inputs where a number of inputs has been filled.
func (r *reedSolomon) DecodeIdx(dst [][]byte, expectInput []bool, input [][]byte) (err error) {
	// Special case: merging mode when expectInput == nil
	if expectInput == nil {
		if len(dst) != len(input) {
			return errors.Join(ErrInvalidInput, errors.New("dst and input must have same length for merging"))
		}
		// XOR each pair of slices
		for i := range dst {
			if input[i] != nil {
				if dst[i] == nil {
					return errors.Join(ErrInvalidInput, fmt.Errorf("input[%d] provided but dst[%d] is nil", i, i))
				}
				if len(dst[i]) != len(input[i]) {
					return errors.Join(ErrInvalidShardSize, fmt.Errorf("dst[%d] size %d != input[%d] size %d", i, len(dst[i]), i, len(input[i])))
				}
				sliceXor(input[i], dst[i], &r.o)
			}
		}
		return nil
	}

	// Validate expectInput length
	if len(expectInput) != r.totalShards {
		return errors.Join(ErrInvalidInput, fmt.Errorf("expectInput length %d, expected %d", len(expectInput), r.totalShards))
	}

	// Validate dst and input have same length as totalShards
	if len(dst) != r.totalShards {
		return errors.Join(ErrInvalidInput, fmt.Errorf("dst length %d, expected %d (totalShards)", len(dst), r.totalShards))
	}
	if len(input) != r.totalShards {
		return errors.Join(ErrInvalidInput, fmt.Errorf("input length %d, expected %d (totalShards)", len(input), r.totalShards))
	}

	// Check for unexpected inputs first
	for inputIdx := range input {
		if input[inputIdx] != nil && !expectInput[inputIdx] {
			return errors.Join(ErrInvalidInput, fmt.Errorf("unexpected input at index %d (not marked in expectInput)", inputIdx))
		}
	}

	// Check that dst shards are not allocated for expected inputs
	for i := range expectInput {
		if expectInput[i] && dst[i] != nil {
			return errors.Join(ErrInvalidInput, fmt.Errorf("dst[%d] should be nil (marked as input in expectInput)", i))
		}
	}

	// Build valid and invalid indices from expectInput
	validIndices := make([]int, 0)
	invalidIndices := make([]int, 0)
	for i, filled := range expectInput {
		if filled {
			validIndices = append(validIndices, i)
		} else {
			invalidIndices = append(invalidIndices, i)
		}
	}

	// We need at least dataShards valid indices for matrix inversion
	if len(validIndices) < r.dataShards {
		return errors.Join(ErrTooFewShards, fmt.Errorf("%d valid shards marked in expectInput, need at least %d", len(validIndices), r.dataShards))
	}

	// Get the inverted matrix for decoding
	dataDecodeMatrix, err := r.getDecodeMatrix(validIndices, invalidIndices)
	if err != nil {
		return err
	}

	// Verify shard sizes are consistent
	shardSize := 0
	for i := range input {
		if input[i] != nil {
			if shardSize == 0 {
				shardSize = len(input[i])
			} else if len(input[i]) != shardSize {
				return errors.Join(ErrInvalidShardSize, fmt.Errorf("input[%d] size %d != expected size %d", i, len(input[i]), shardSize))
			}
		}
	}
	for i := range dst {
		if dst[i] != nil {
			if len(dst[i]) != shardSize {
				return errors.Join(ErrInvalidShardSize, fmt.Errorf("dst[%d] size %d != expected size %d", i, len(dst[i]), shardSize))
			}
		}
	}

	// Build matrix rows and output arrays for codeSomeShards
	outputCount := 0
	outputs := make([][]byte, 0, r.totalShards)
	matrixRows := make([][]byte, 0, r.totalShards)

	for dstIdx := range dst {
		if dst[dstIdx] == nil {
			continue
		}
		outputs = append(outputs, dst[dstIdx])

		// Build matrix row for this dst shard
		row := make([]byte, len(validIndices))
		if dstIdx < r.dataShards {
			// Data shard: use inverted matrix row directly
			copy(row, dataDecodeMatrix[dstIdx])
		} else {
			// Parity shard: multiply parity row with inverted matrix
			parityIdx := dstIdx - r.dataShards
			for col := 0; col < len(validIndices) && col < r.dataShards; col++ {
				row[col] = 0
				for i := 0; i < r.dataShards; i++ {
					row[col] ^= galMultiply(r.parity[parityIdx][i], dataDecodeMatrix[i][col])
				}
			}
		}
		matrixRows = append(matrixRows, row)
		outputCount++
	}

	if outputCount == 0 {
		return nil
	}

	// Build input array from available shards
	inputs := make([][]byte, 0, len(validIndices))
	actualInputIndices := make([]int, 0, len(validIndices))
	for col := 0; col < len(validIndices); col++ {
		inputIdx := validIndices[col]
		if inputIdx < len(input) && input[inputIdx] != nil {
			inputs = append(inputs, input[inputIdx])
			actualInputIndices = append(actualInputIndices, col)
		}
	}

	// If no inputs are provided in this call, nothing to do
	if len(inputs) == 0 {
		return nil
	}

	// Adjust matrix rows to only include columns for available inputs
	adjustedMatrixRows := make([][]byte, len(matrixRows))
	for i, row := range matrixRows {
		adjustedRow := make([]byte, len(actualInputIndices))
		for j, colIdx := range actualInputIndices {
			adjustedRow[j] = row[colIdx]
		}
		adjustedMatrixRows[i] = adjustedRow
	}

	// Use codeSomeShards for efficient matrix multiplication
	// Always accumulate (never clear) - caller provides zeroed slices
	r.codeSomeShards(adjustedMatrixRows, inputs, outputs, shardSize, false)
	return nil
}

// ReconstructData will recreate any missing data shards, if possible.
//
// Given a list of shards, some of which contain data, fills in the
// data shards that don't have data.
//
// The length of the array must be equal to shards.
// You indicate that a shard is missing by setting it to nil or zero-length.
// If a shard is zero-length but has sufficient capacity, that memory will
// be used, otherwise a new []byte will be allocated.
//
// If there are too few shards to reconstruct the missing
// ones, ErrTooFewShards will be returned.
//
// As the reconstructed shard set may contain missing parity shards,
// calling the Verify function is likely to fail.
func (r *reedSolomon) ReconstructData(shards [][]byte) error {
	return r.reconstruct(shards, true, nil)
}

// ReconstructSome will recreate only requested shards, if possible.
//
// Given a list of shards, some of which contain data, fills in the
// shards indicated by true values in the "required" parameter.
// The length of the "required" array must be equal to either Shards or DataShards.
// If the length is equal to DataShards, the reconstruction of parity shards will be ignored.
//
// The length of "shards" array must be equal to Shards.
// You indicate that a shard is missing by setting it to nil or zero-length.
// If a shard is zero-length but has sufficient capacity, that memory will
// be used, otherwise a new []byte will be allocated.
//
// If there are too few shards to reconstruct the missing
// ones, ErrTooFewShards will be returned.
//
// As the reconstructed shard set may contain missing parity shards,
// calling the Verify function is likely to fail.
func (r *reedSolomon) ReconstructSome(shards [][]byte, required []bool) error {
	if len(required) != r.dataShards && len(required) != r.totalShards {
		return ErrInvalidInput
	}
	return r.reconstruct(shards, len(required) == r.dataShards, required)
}

// reconstruct will recreate the missing data totalShards, and unless
// dataOnly is true, also the missing parity totalShards
//
// The length of "shards" array must be equal to totalShards.
// You indicate that a shard is missing by setting it to nil.
//
// If there are too few totalShards to reconstruct the missing
// ones, ErrTooFewShards will be returned.
func (r *reedSolomon) reconstruct(shards [][]byte, dataOnly bool, required []bool) error {
	if len(shards) != r.totalShards || required != nil && len(required) < r.dataShards {
		return ErrTooFewShards
	}
	// Check arguments.
	err := checkShards(shards, true)
	if err != nil {
		return err
	}

	shardSize := shardSize(shards)

	// Quick check: are all of the shards present?  If so, there's
	// nothing to do.
	numberPresent := 0
	dataPresent := 0
	missingRequired := 0
	for i := 0; i < r.totalShards; i++ {
		if len(shards[i]) != 0 {
			numberPresent++
			if i < r.dataShards {
				dataPresent++
			}
		} else if required != nil {
			if required[i] {
				missingRequired++
			}
		}
	}
	if numberPresent == r.totalShards || dataOnly && dataPresent == r.dataShards ||
		required != nil && missingRequired == 0 {
		// Cool. All of the shards have data. We don't
		// need to do anything.
		return nil
	}

	// More complete sanity check
	if numberPresent < r.dataShards {
		return ErrTooFewShards
	}

	// Pull out an array holding just the shards that
	// correspond to the rows of the submatrix.  These shards
	// will be the input to the decoding process that re-creates
	// the missing data shards.
	//
	// Also, create an array of indices of the valid rows we do have
	// and the invalid rows we don't have up until we have enough valid rows.
	subShards := make([][]byte, r.dataShards)
	validIndices := make([]int, r.dataShards)
	invalidIndices := make([]int, 0)
	subMatrixRow := 0
	for matrixRow := 0; matrixRow < r.totalShards && subMatrixRow < r.dataShards; matrixRow++ {
		if len(shards[matrixRow]) != 0 {
			subShards[subMatrixRow] = shards[matrixRow]
			validIndices[subMatrixRow] = matrixRow
			subMatrixRow++
		} else {
			invalidIndices = append(invalidIndices, matrixRow)
		}
	}

	// Get the inverted matrix for decoding
	dataDecodeMatrix, err := r.getDecodeMatrix(validIndices, invalidIndices)
	if err != nil {
		return err
	}

	// Unified reconstruction: build a single decode matrix for all missing shards
	// and reconstruct them in one codeSomeShards call.
	outputCount := 0
	outputs := make([][]byte, r.totalShards)
	matrixRows := make([][]byte, r.totalShards)

	// Count and prepare missing data shards
	for iShard := 0; iShard < r.dataShards; iShard++ {
		if len(shards[iShard]) == 0 && (required == nil || required[iShard]) {
			if cap(shards[iShard]) >= shardSize {
				shards[iShard] = shards[iShard][0:shardSize]
			} else {
				shards[iShard] = AllocAligned(1, shardSize)[0]
			}
			outputs[outputCount] = shards[iShard]
			matrixRows[outputCount] = dataDecodeMatrix[iShard]
			outputCount++
		}
	}

	if !dataOnly {
		// Count and prepare missing parity shards
		for iShard := r.dataShards; iShard < r.totalShards; iShard++ {
			if len(shards[iShard]) == 0 && (required == nil || required[iShard]) {
				if cap(shards[iShard]) >= shardSize {
					shards[iShard] = shards[iShard][0:shardSize]
				} else {
					shards[iShard] = AllocAligned(1, shardSize)[0]
				}
				outputs[outputCount] = shards[iShard]
				// For parity shards, multiply parity row with inverted matrix
				parityIdx := iShard - r.dataShards
				matrixRows[outputCount] = multiplyRowWithMatrix(r.parity[parityIdx], dataDecodeMatrix)
				outputCount++
			}
		}
	}

	// Single reconstruction call for all missing shards
	if outputCount > 0 {
		r.codeSomeShards(matrixRows[:outputCount], subShards, outputs[:outputCount], shardSize, true)
	}
	return nil
}

// ErrShortData will be returned by Split(), if there isn't enough data
// to fill the number of shards.
var ErrShortData = errors.New("not enough data to fill the number of requested shards")

// Split a data slice into the number of shards given to the encoder,
// and create empty parity shards if necessary.
//
// The data will be split into equally sized shards.
// If the data size isn't divisible by the number of shards,
// the last shard will contain extra zeros.
//
// If there is extra capacity on the provided data slice
// it will be used instead of allocating parity shards.
// It will be zeroed out.
//
// There must be at least 1 byte otherwise ErrShortData will be
// returned.
//
// The data will not be copied, except for the last shard, so you
// should not modify the data of the input slice afterwards.
func (r *reedSolomon) Split(data []byte) ([][]byte, error) {
	if len(data) == 0 {
		return nil, ErrShortData
	}
	if r.totalShards == 1 {
		return [][]byte{data}, nil
	}

	dataLen := len(data)
	// Calculate number of bytes per data shard.
	perShard := (len(data) + r.dataShards - 1) / r.dataShards
	needTotal := r.totalShards * perShard

	if cap(data) > len(data) {
		if cap(data) > needTotal {
			data = data[:needTotal]
		} else {
			data = data[:cap(data)]
		}
		clear(data[dataLen:])
	}

	// Only allocate memory if necessary
	var padding [][]byte
	if len(data) < needTotal {
		// calculate maximum number of full shards in `data` slice
		fullShards := len(data) / perShard
		padding = AllocAligned(r.totalShards-fullShards, perShard)

		if dataLen > perShard*fullShards {
			// Copy partial shards
			copyFrom := data[perShard*fullShards : dataLen]
			for i := range padding {
				if len(copyFrom) == 0 {
					break
				}
				copyFrom = copyFrom[copy(padding[i], copyFrom):]
			}
		}
	}

	// Split into equal-length shards.
	dst := make([][]byte, r.totalShards)
	i := 0
	for ; i < len(dst) && len(data) >= perShard; i++ {
		dst[i] = data[:perShard:perShard]
		data = data[perShard:]
	}

	for j := 0; i+j < len(dst); j++ {
		dst[i+j] = padding[0]
		padding = padding[1:]
	}

	return dst, nil
}

// ErrReconstructRequired is returned if too few data shards are intact and a
// reconstruction is required before you can successfully join the shards.
var ErrReconstructRequired = errors.New("reconstruction required as one or more required data shards are nil")

// Join the shards and write the data segment to dst.
//
// Only the data shards are considered.
// You must supply the exact output size you want.
//
// If there are to few shards given, ErrTooFewShards will be returned.
// If the total data size is less than outSize, ErrShortData will be returned.
// If one or more required data shards are nil, ErrReconstructRequired will be returned.
func (r *reedSolomon) Join(dst io.Writer, shards [][]byte, outSize int) error {
	// Do we have enough shards?
	if len(shards) < r.dataShards {
		return ErrTooFewShards
	}
	shards = shards[:r.dataShards]

	// Do we have enough data?
	size := 0
	for _, shard := range shards {
		if shard == nil {
			return ErrReconstructRequired
		}
		size += len(shard)

		// Do we have enough data already?
		if size >= outSize {
			break
		}
	}
	if size < outSize {
		return ErrShortData
	}

	// Copy data to dst
	write := outSize
	for _, shard := range shards {
		if write < len(shard) {
			_, err := dst.Write(shard[:write])
			return err
		}
		n, err := dst.Write(shard)
		if err != nil {
			return err
		}
		write -= n
	}
	return nil
}

type indexer interface {
	int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64
}
