package wazevoapi

const poolPageSize = 128

// Pool is a pool of T that can be allocated and reset.
// This is useful to avoid unnecessary allocations.
type Pool[ any] struct {
	pages            []*[poolPageSize]
	resetFn          func(*)
	allocated, index int
}

// NewPool returns a new Pool.
// resetFn is called when a new T is allocated in Pool.Allocate.
func [ any]( func(*)) Pool[] {
	var  Pool[]
	.resetFn = 
	.Reset()
	return 
}

// Allocated returns the number of allocated T currently in the pool.
func ( *Pool[]) () int {
	return .allocated
}

// Allocate allocates a new T from the pool.
func ( *Pool[]) () * {
	if .index == poolPageSize {
		if len(.pages) == cap(.pages) {
			.pages = append(.pages, new([poolPageSize]))
		} else {
			 := len(.pages)
			.pages = .pages[:+1]
			if .pages[] == nil {
				.pages[] = new([poolPageSize])
			}
		}
		.index = 0
	}
	 := &.pages[len(.pages)-1][.index]
	if .resetFn != nil {
		.resetFn()
	}
	.index++
	.allocated++
	return 
}

// View returns the pointer to i-th item from the pool.
func ( *Pool[]) ( int) * {
	,  := /poolPageSize, %poolPageSize
	return &.pages[][]
}

// Reset resets the pool.
func ( *Pool[]) () {
	.pages = .pages[:0]
	.index = poolPageSize
	.allocated = 0
}

// IDedPool is a pool of T that can be allocated and reset, with a way to get T by an ID.
type IDedPool[ any] struct {
	pool             Pool[]
	idToItems        []*
	maxIDEncountered int
}

// NewIDedPool returns a new IDedPool.
func [ any]( func(*)) IDedPool[] {
	return IDedPool[]{pool: NewPool[](), maxIDEncountered: -1}
}

// GetOrAllocate returns the T with the given id.
func ( *IDedPool[]) ( int) * {
	if .maxIDEncountered <  {
		.maxIDEncountered = 
	}
	if  >= len(.idToItems) {
		.idToItems = append(.idToItems, make([]*, -len(.idToItems)+1)...)
	}
	if .idToItems[] == nil {
		.idToItems[] = .pool.Allocate()
	}
	return .idToItems[]
}

// Get returns the T with the given id, or nil if it's not allocated.
func ( *IDedPool[]) ( int) * {
	if  >= len(.idToItems) {
		return nil
	}
	return .idToItems[]
}

// Reset resets the pool.
func ( *IDedPool[]) () {
	.pool.Reset()
	for  := 0;  <= .maxIDEncountered; ++ {
		.idToItems[] = nil
	}
	.maxIDEncountered = -1
}

// MaxIDEncountered returns the maximum id encountered so far.
func ( *IDedPool[]) () int {
	return .maxIDEncountered
}

// arraySize is the size of the array used in VarLengthPool's arrayPool.
// This is chosen to be 8, which is empirically a good number among 8, 12, 16 and 20.
const arraySize = 8

// VarLengthPool is a pool of VarLength[T] that can be allocated and reset.
type (
	VarLengthPool[ any] struct {
		arrayPool Pool[varLengthPoolArray[]]
		slicePool Pool[[]]
	}
	// varLengthPoolArray wraps an array and keeps track of the next index to be used to avoid the heap allocation.
	varLengthPoolArray[ any] struct {
		arr  [arraySize]
		next int
	}
)

// VarLength is a variable length array that can be reused via a pool.
type VarLength[ any] struct {
	arr *varLengthPoolArray[]
	slc *[]
}

// NewVarLengthPool returns a new VarLengthPool.
func [ any]() VarLengthPool[] {
	return VarLengthPool[]{
		arrayPool: NewPool[varLengthPoolArray[]](func( *varLengthPoolArray[]) {
			.next = 0
		}),
		slicePool: NewPool[[]](func( *[]) {
			* = (*)[:0]
		}),
	}
}

// NewNilVarLength returns a new VarLength[T] with a nil backing.
func [ any]() VarLength[] {
	return VarLength[]{}
}

// Allocate allocates a new VarLength[T] from the pool.
func ( *VarLengthPool[]) ( int) VarLength[] {
	if  <= arraySize {
		 := .arrayPool.Allocate()
		return VarLength[]{arr: }
	}
	 := .slicePool.Allocate()
	return VarLength[]{slc: }
}

// Reset resets the pool.
func ( *VarLengthPool[]) () {
	.arrayPool.Reset()
	.slicePool.Reset()
}

// Append appends items to the backing slice just like the `append` builtin function in Go.
func ( VarLength[]) ( *VarLengthPool[],  ...) VarLength[] {
	if .slc != nil {
		*.slc = append(*.slc, ...)
		return 
	}

	if .arr == nil {
		.arr = .arrayPool.Allocate()
	}

	 := .arr
	if .next+len() <= arraySize {
		for ,  := range  {
			.arr[.next] = 
			.next++
		}
	} else {
		 := .slicePool.Allocate()
		// Copy the array to the slice.
		for  := 0;  < .next; ++ {
			* = append(*, .arr[])
		}
		.slc = 
		*.slc = append(*.slc, ...)
	}
	return 
}

// View returns the backing slice.
func ( VarLength[]) () [] {
	if .slc != nil {
		return *.slc
	} else if .arr != nil {
		 := .arr
		return .arr[:.next]
	}
	return nil
}

// Cut cuts the backing slice to the given length.
// Precondition: n <= len(i.backing).
func ( VarLength[]) ( int) {
	if .slc != nil {
		*.slc = (*.slc)[:]
	} else if .arr != nil {
		.arr.next = 
	}
}