// Copyright 2020-2025 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package linker

import "google.golang.org/protobuf/types/descriptorpb"

// allocPool helps allocate descriptor instances. Instead of allocating
// them one at a time, we allocate a pool -- a large, flat slice to hold
// all descriptors of a particular kind for a file. We then use capacity
// in the pool when we need space for individual descriptors.
type allocPool struct {
	numMessages   int
	numFields     int
	numOneofs     int
	numEnums      int
	numEnumValues int
	numExtensions int
	numServices   int
	numMethods    int

	messages   []msgDescriptor
	fields     []fldDescriptor
	oneofs     []oneofDescriptor
	enums      []enumDescriptor
	enumVals   []enValDescriptor
	extensions []extTypeDescriptor
	services   []svcDescriptor
	methods    []mtdDescriptor
}

func newAllocPool(file *descriptorpb.FileDescriptorProto) *allocPool {
	var pool allocPool
	pool.countElements(file)
	pool.messages = make([]msgDescriptor, pool.numMessages)
	pool.fields = make([]fldDescriptor, pool.numFields)
	pool.oneofs = make([]oneofDescriptor, pool.numOneofs)
	pool.enums = make([]enumDescriptor, pool.numEnums)
	pool.enumVals = make([]enValDescriptor, pool.numEnumValues)
	pool.extensions = make([]extTypeDescriptor, pool.numExtensions)
	pool.services = make([]svcDescriptor, pool.numServices)
	pool.methods = make([]mtdDescriptor, pool.numMethods)
	return &pool
}

func (p *allocPool) getMessages(count int) []msgDescriptor {
	allocated := p.messages[:count]
	p.messages = p.messages[count:]
	return allocated
}

func (p *allocPool) getFields(count int) []fldDescriptor {
	allocated := p.fields[:count]
	p.fields = p.fields[count:]
	return allocated
}

func (p *allocPool) getOneofs(count int) []oneofDescriptor {
	allocated := p.oneofs[:count]
	p.oneofs = p.oneofs[count:]
	return allocated
}

func (p *allocPool) getEnums(count int) []enumDescriptor {
	allocated := p.enums[:count]
	p.enums = p.enums[count:]
	return allocated
}

func (p *allocPool) getEnumValues(count int) []enValDescriptor {
	allocated := p.enumVals[:count]
	p.enumVals = p.enumVals[count:]
	return allocated
}

func (p *allocPool) getExtensions(count int) []extTypeDescriptor {
	allocated := p.extensions[:count]
	p.extensions = p.extensions[count:]
	return allocated
}

func (p *allocPool) getServices(count int) []svcDescriptor {
	allocated := p.services[:count]
	p.services = p.services[count:]
	return allocated
}

func (p *allocPool) getMethods(count int) []mtdDescriptor {
	allocated := p.methods[:count]
	p.methods = p.methods[count:]
	return allocated
}

func (p *allocPool) countElements(file *descriptorpb.FileDescriptorProto) {
	p.countElementsInMessages(file.MessageType)
	p.countElementsInEnums(file.EnumType)
	p.numExtensions += len(file.Extension)
	p.numServices += len(file.Service)
	for _, svc := range file.Service {
		p.numMethods += len(svc.Method)
	}
}

func (p *allocPool) countElementsInMessages(msgs []*descriptorpb.DescriptorProto) {
	p.numMessages += len(msgs)
	for _, msg := range msgs {
		p.numFields += len(msg.Field)
		p.numOneofs += len(msg.OneofDecl)
		p.countElementsInMessages(msg.NestedType)
		p.countElementsInEnums(msg.EnumType)
		p.numExtensions += len(msg.Extension)
	}
}

func (p *allocPool) countElementsInEnums(enums []*descriptorpb.EnumDescriptorProto) {
	p.numEnums += len(enums)
	for _, enum := range enums {
		p.numEnumValues += len(enum.Value)
	}
}
