package erasurecode

import (
	"errors"

	objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
	"github.com/klauspost/reedsolomon"
)

var (
	// ErrMalformedSlice is returned when a slice of EC chunks is inconsistent.
	ErrMalformedSlice = errors.New("inconsistent EC headers")
	// ErrInvShardNum is returned from NewConstructor when the number of shards is invalid.
	ErrInvShardNum = reedsolomon.ErrInvShardNum
	// ErrMaxShardNum is returned from NewConstructor when the number of shards is too big.
	ErrMaxShardNum = reedsolomon.ErrMaxShardNum
)

// MaxShardCount is the maximum number of shards.
const MaxShardCount = 256

// Constructor is a wrapper around encoder allowing to reconstruct objects.
// It's methods are not thread-safe.
type Constructor struct {
	enc           reedsolomon.Encoder
	headerLength  uint32
	payloadShards [][]byte
	headerShards  [][]byte
}

// NewConstructor returns new constructor instance.
func NewConstructor(dataCount int, parityCount int) (*Constructor, error) {
	// The library supports up to 65536 shards with some restrictions.
	// This can easily result in OOM or panic, thus SDK declares it's own restriction.
	if dataCount+parityCount > MaxShardCount {
		return nil, ErrMaxShardNum
	}

	enc, err := reedsolomon.New(dataCount, parityCount)
	if err != nil {
		return nil, err
	}
	return &Constructor{enc: enc}, nil
}

// clear clears internal state of the constructor, so it can be reused.
func (c *Constructor) clear() {
	c.headerLength = 0
	c.payloadShards = nil
	c.headerShards = nil
}

func (c *Constructor) fillHeader(parts []*objectSDK.Object) error {
	shards := make([][]byte, len(parts))
	headerLength := 0
	for i := range parts {
		if parts[i] == nil {
			continue
		}

		var err error
		headerLength, err = validatePart(parts, i, headerLength)
		if err != nil {
			return err
		}

		shards[i] = parts[i].GetECHeader().Header()
	}

	c.headerLength = uint32(headerLength)
	c.headerShards = shards
	return nil
}

// fillPayload fills the payload shards.
// Currently there is no case when it can be called without reconstructing header,
// thus fillHeader() must be called before and this function performs no validation.
func (c *Constructor) fillPayload(parts []*objectSDK.Object) {
	shards := make([][]byte, len(parts))
	for i := range parts {
		if parts[i] == nil {
			continue
		}
		shards[i] = parts[i].Payload()
	}
	c.payloadShards = shards
}