/*
 * SPDX-FileCopyrightText: © Hypermode Inc. <hello@hypermode.com>
 * SPDX-License-Identifier: Apache-2.0
 */

package z

import (
	
	
	
	
	
	
	
	
	
	

	
)

// Allocator amortizes the cost of small allocations by allocating memory in
// bigger chunks.  Internally it uses z.Calloc to allocate memory. Once
// allocated, the memory is not moved, so it is safe to use the allocated bytes
// to unsafe cast them to Go struct pointers. Maintaining a freelist is slow.
// Instead, Allocator only allocates memory, with the idea that finally we
// would just release the entire Allocator.
type Allocator struct {
	sync.Mutex
	compIdx uint64 // Stores bufIdx in 32 MSBs and posIdx in 32 LSBs.
	buffers [][]byte
	Ref     uint64
	Tag     string
}

// allocs keeps references to all Allocators, so we can safely discard them later.
var allocsMu *sync.Mutex
var allocRef uint64
var allocs map[uint64]*Allocator
var calculatedLog2 []int

func init() {
	allocsMu = new(sync.Mutex)
	allocs = make(map[uint64]*Allocator)

	// Set up a unique Ref per process.
	allocRef = uint64(rand.Int63n(1<<16)) << 48
	calculatedLog2 = make([]int, 1025)
	for  := 1;  <= 1024; ++ {
		calculatedLog2[] = int(math.Log2(float64()))
	}
}

// NewAllocator creates an allocator starting with the given size.
func ( int,  string) *Allocator {
	 := atomic.AddUint64(&allocRef, 1)
	// We should not allow a zero sized page because addBufferWithMinSize
	// will run into an infinite loop trying to double the pagesize.
	if  < 512 {
		 = 512
	}
	 := &Allocator{
		Ref:     ,
		buffers: make([][]byte, 64),
		Tag:     ,
	}
	 := uint64(log2())
	if bits.OnesCount64(uint64()) > 1 {
		 += 1
	}
	.buffers[0] = Calloc(1<<, .Tag)

	allocsMu.Lock()
	allocs[] = 
	allocsMu.Unlock()
	return 
}

func ( *Allocator) () {
	atomic.StoreUint64(&.compIdx, 0)
}

func () string {
	allocsMu.Lock()
	 := make(map[string]uint64)
	 := make(map[string]int)
	for ,  := range allocs {
		[.Tag] += .Allocated()
		[.Tag] += 1
	}

	var  bytes.Buffer
	for ,  := range  {
		fmt.Fprintf(&, "Tag: %s Num: %d Size: %s . ", , [], humanize.IBytes())
	}
	allocsMu.Unlock()
	return .String()
}

func ( *Allocator) () string {
	var  strings.Builder
	.WriteString(fmt.Sprintf("Allocator: %x\n", .Ref))
	var  int
	for ,  := range .buffers {
		 += len()
		if len() == 0 {
			break
		}
		.WriteString(fmt.Sprintf("idx: %d len: %d cum: %d\n", , len(), ))
	}
	 := atomic.LoadUint64(&.compIdx)
	,  := parse()
	.WriteString(fmt.Sprintf("bi: %d pi: %d\n", , ))
	.WriteString(fmt.Sprintf("Size: %d\n", .Size()))
	return .String()
}

// AllocatorFrom would return the allocator corresponding to the ref.
func ( uint64) *Allocator {
	allocsMu.Lock()
	 := allocs[]
	allocsMu.Unlock()
	return 
}

func parse( uint64) (,  int) {
	return int( >> 32), int( & 0xFFFFFFFF)
}

// Size returns the size of the allocations so far.
func ( *Allocator) () int {
	 := atomic.LoadUint64(&.compIdx)
	,  := parse()
	var  int
	for ,  := range .buffers {
		if  <  {
			 += len()
			continue
		}
		 += 
		return 
	}
	panic("Size should not reach here")
}

func log2( int) int {
	if  < len(calculatedLog2) {
		return calculatedLog2[]
	}
	 := 10
	 >>= 10
	for  > 1 {
		 >>= 1
		++
	}
	return 
}

func ( *Allocator) () uint64 {
	var  int
	for ,  := range .buffers {
		 += cap()
	}
	return uint64()
}

func ( *Allocator) ( int) {
	var  int
	for ,  := range .buffers {
		if len() == 0 {
			break
		}
		 += len()
		if  <  {
			continue
		}
		Free()
		.buffers[] = nil
	}
}

// Release would release the memory back. Remember to make this call to avoid memory leaks.
func ( *Allocator) () {
	if  == nil {
		return
	}

	var  int
	for ,  := range .buffers {
		if len() == 0 {
			break
		}
		 += len()
		Free()
	}

	allocsMu.Lock()
	delete(allocs, .Ref)
	allocsMu.Unlock()
}

const maxAlloc = 1 << 30

func ( *Allocator) () int {
	return maxAlloc
}

const nodeAlign = unsafe.Sizeof(uint64(0)) - 1

func ( *Allocator) ( int) []byte {
	 :=  + int(nodeAlign)
	 := .Allocate()
	// We are reusing allocators. In that case, it's important to zero out the memory allocated
	// here. We don't always zero it out (in Allocate), because other functions would be immediately
	// overwriting the allocated slices anyway (see Copy).
	ZeroOut(, 0, len())

	 := uintptr(unsafe.Pointer(&[0]))
	 := ( + nodeAlign) & ^nodeAlign
	 := int( - )

	return [ : +]
}

func ( *Allocator) ( []byte) []byte {
	if  == nil {
		return append([]byte{}, ...)
	}
	 := .Allocate(len())
	copy(, )
	return 
}

func ( *Allocator) (,  int) {
	for {
		if  >= len(.buffers) {
			panic(fmt.Sprintf("Allocator can not allocate more than %d buffers", len(.buffers)))
		}
		if len(.buffers[]) == 0 {
			break
		}
		if  <= len(.buffers[]) {
			// No need to do anything. We already have a buffer which can satisfy minSz.
			return
		}
		++
	}
	assert( > 0)
	// We need to allocate a new buffer.
	// Make pageSize double of the last allocation.
	 := 2 * len(.buffers[-1])
	// Ensure pageSize is bigger than sz.
	for  <  {
		 *= 2
	}
	// If bigger than maxAlloc, trim to maxAlloc.
	if  > maxAlloc {
		 = maxAlloc
	}

	 := Calloc(, .Tag)
	assert(len(.buffers[]) == 0)
	.buffers[] = 
}

func ( *Allocator) ( int) []byte {
	if  == nil {
		return make([]byte, )
	}
	if  > maxAlloc {
		panic(fmt.Sprintf("Unable to allocate more than %d\n", maxAlloc))
	}
	if  == 0 {
		return nil
	}
	for {
		 := atomic.AddUint64(&.compIdx, uint64())
		,  := parse()
		 := .buffers[]
		if  > len() {
			.Lock()
			 := atomic.LoadUint64(&.compIdx)
			,  := parse()
			if  !=  {
				.Unlock()
				continue
			}
			.addBufferAt(+1, )
			atomic.StoreUint64(&.compIdx, uint64((+1)<<32))
			.Unlock()
			// We added a new buffer. Let's acquire slice the right way by going back to the top.
			continue
		}
		 := [- : ]
		return 
	}
}

type AllocatorPool struct {
	numGets int64
	allocCh chan *Allocator
	closer  *Closer
}

func ( int) *AllocatorPool {
	 := &AllocatorPool{
		allocCh: make(chan *Allocator, ),
		closer:  NewCloser(1),
	}
	go .freeupAllocators()
	return 
}

func ( *AllocatorPool) ( int,  string) *Allocator {
	if  == nil {
		return NewAllocator(, )
	}
	atomic.AddInt64(&.numGets, 1)
	select {
	case  := <-.allocCh:
		.Reset()
		.Tag = 
		return 
	default:
		return NewAllocator(, )
	}
}
func ( *AllocatorPool) ( *Allocator) {
	if  == nil {
		return
	}
	if  == nil {
		.Release()
		return
	}
	.TrimTo(400 << 20)

	select {
	case .allocCh <- :
		return
	default:
		.Release()
	}
}

func ( *AllocatorPool) () {
	if  == nil {
		return
	}
	.closer.SignalAndWait()
}

func ( *AllocatorPool) () {
	defer .closer.Done()

	 := time.NewTicker(2 * time.Second)
	defer .Stop()

	 := func() bool {
		select {
		case  := <-.allocCh:
			.Release()
			return true
		default:
			return false
		}
	}

	var  int64
	for {
		select {
		case <-.closer.HasBeenClosed():
			close(.allocCh)
			for  := range .allocCh {
				.Release()
			}
			return

		case <-.C:
			 := atomic.LoadInt64(&.numGets)
			if  !=  {
				// Some retrievals were made since the last time. So, let's avoid doing a release.
				 = 
				continue
			}
			()
		}
	}
}