package avro

import (
	
	
	

	
)

const (
	defaultMaxByteSliceSize = 1_048_576 // 1 MiB
)

// DefaultConfig is the default API.
var DefaultConfig = Config{}.Freeze()

// Config customises how the codec should behave.
type Config struct {
	// TagKey is the struct tag key used when en/decoding structs.
	// This defaults to "avro".
	TagKey string

	// BlockLength is the length of blocks for maps and arrays.
	// This defaults to 100.
	BlockLength int

	// DisableBlockSizeHeader disables encoding of an array/map size in bytes.
	// Encoded array/map will be prefixed with only the number of elements in
	// contrast with default behavior which prefixes them with the number of elements
	// and the total number of bytes in the array/map. Both approaches are valid according to the
	// Avro specification, however not all decoders support the latter.
	DisableBlockSizeHeader bool

	// UnionResolutionError determines if an error will be returned
	// when a type cannot be resolved while decoding a union.
	UnionResolutionError bool

	// PartialUnionTypeResolution dictates if the union type resolution
	// should be attempted even when not all union types are registered.
	// When enabled, the underlying type will get resolved if it is registered
	// even if other types of the union are not. If resolution fails, logic
	// falls back to default union resolution behavior based on the value of
	// UnionResolutionError.
	PartialUnionTypeResolution bool

	// Disable caching layer for encoders and decoders, forcing them to get rebuilt on every
	// call to Marshal() and Unmarshal()
	DisableCaching bool

	// MaxByteSliceSize is the maximum size of `bytes` or `string` types the Reader will create, defaulting to 1MiB.
	// If this size is exceeded, the Reader returns an error. This can be disabled by setting a negative number.
	MaxByteSliceSize int

	// MaxSliceAllocSize is the maximum size that the decoder will allocate, set to the max heap
	// allocation size by default.
	// If this size is exceeded, the decoder returns an error.
	MaxSliceAllocSize int
}

// Freeze makes the configuration immutable.
func ( Config) () API {
	 := &frozenConfig{
		config:         ,
		resolver:       NewTypeResolver(),
		typeConverters: NewTypeConverters(),
	}

	.readerPool = &sync.Pool{
		New: func() any {
			return &Reader{
				cfg:    ,
				reader: nil,
				buf:    nil,
				head:   0,
				tail:   0,
			}
		},
	}
	.writerPool = &sync.Pool{
		New: func() any {
			return &Writer{
				cfg:   ,
				out:   nil,
				buf:   make([]byte, 0, 512),
				Error: nil,
			}
		},
	}

	return 
}

// API represents a frozen Config.
type API interface {
	// Marshal returns the Avro encoding of v.
	Marshal(schema Schema, v any) ([]byte, error)

	// Unmarshal parses the Avro encoded data and stores the result in the value pointed to by v.
	// If v is nil or not a pointer, Unmarshal returns an error.
	Unmarshal(schema Schema, data []byte, v any) error

	// NewEncoder returns a new encoder that writes to w using schema.
	NewEncoder(schema Schema, w io.Writer) *Encoder

	// NewDecoder returns a new decoder that reads from reader r using schema.
	NewDecoder(schema Schema, r io.Reader) *Decoder

	// DecoderOf returns the value decoder for a given schema and type.
	DecoderOf(schema Schema, typ reflect2.Type) ValDecoder

	// EncoderOf returns the value encoder for a given schema and type.
	EncoderOf(schema Schema, typ reflect2.Type) ValEncoder

	// Register registers names to their types for resolution. All primitive types are pre-registered.
	Register(name string, obj any)

	// RegisterTypeConverters registers type conversion functions.
	RegisterTypeConverters(conv ...TypeConverter)
}

type frozenConfig struct {
	config Config

	decoderCache sync.Map // map[cacheKey]ValDecoder
	encoderCache sync.Map // map[cacheKey]ValEncoder

	readerPool *sync.Pool
	writerPool *sync.Pool

	resolver *TypeResolver

	typeConverters *TypeConverters
}

func ( *frozenConfig) ( Schema,  any) ([]byte, error) {
	 := .borrowWriter()
	defer .returnWriter()

	.WriteVal(, )
	if  := .Error;  != nil {
		return nil, 
	}

	 := .Buffer()
	 := make([]byte, len())
	copy(, )

	return , nil
}

func ( *frozenConfig) () *Writer {
	 := .writerPool.Get().(*Writer)
	.Reset(nil)
	return 
}

func ( *frozenConfig) ( *Writer) {
	.out = nil
	.Error = nil

	.writerPool.Put()
}

func ( *frozenConfig) ( Schema,  []byte,  any) error {
	 := .borrowReader()
	defer .returnReader()

	.ReadVal(, )
	 := .Error

	if errors.Is(, io.EOF) {
		return nil
	}

	return 
}

func ( *frozenConfig) ( []byte) *Reader {
	 := .readerPool.Get().(*Reader)
	.Reset()
	return 
}

func ( *frozenConfig) ( *Reader) {
	.Error = nil
	.readerPool.Put()
}

func ( *frozenConfig) ( Schema,  io.Writer) *Encoder {
	,  := .(*Writer)
	if ! {
		 = NewWriter(, 512, WithWriterConfig())
	}
	return &Encoder{
		s: ,
		w: ,
	}
}

func ( *frozenConfig) ( Schema,  io.Reader) *Decoder {
	 := NewReader(, 512, WithReaderConfig())
	return &Decoder{
		s: ,
		r: ,
	}
}

func ( *frozenConfig) ( string,  any) {
	.resolver.Register(, )
}

func ( *frozenConfig) ( ...TypeConverter) {
	.typeConverters.RegisterTypeConverters(...)
}

type cacheKey struct {
	fingerprint [32]byte
	rtype       uintptr
}

func ( *frozenConfig) ( [32]byte,  uintptr,  ValDecoder) {
	if .config.DisableCaching {
		return
	}
	 := cacheKey{fingerprint: , rtype: }
	.decoderCache.Store(, )
}

func ( *frozenConfig) ( [32]byte,  uintptr) ValDecoder {
	if .config.DisableCaching {
		return nil
	}
	 := cacheKey{fingerprint: , rtype: }
	if ,  := .decoderCache.Load();  {
		return .(ValDecoder)
	}

	return nil
}

func ( *frozenConfig) ( [32]byte,  uintptr,  ValEncoder) {
	if .config.DisableCaching {
		return
	}
	 := cacheKey{fingerprint: , rtype: }
	.encoderCache.Store(, )
}

func ( *frozenConfig) ( [32]byte,  uintptr) ValEncoder {
	if .config.DisableCaching {
		return nil
	}
	 := cacheKey{fingerprint: , rtype: }
	if ,  := .encoderCache.Load();  {
		return .(ValEncoder)
	}

	return nil
}

func ( *frozenConfig) () string {
	 := .config.TagKey
	if  == "" {
		return "avro"
	}
	return 
}

func ( *frozenConfig) () int {
	 := .config.BlockLength
	if  <= 0 {
		return 100
	}
	return 
}

func ( *frozenConfig) () int {
	 := .config.MaxByteSliceSize
	if  == 0 {
		return defaultMaxByteSliceSize
	}
	return 
}

func ( *frozenConfig) () int {
	 := .config.MaxSliceAllocSize
	if  > maxAllocSize ||  <= 0 {
		return maxAllocSize
	}
	return 
}