// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package lru

import (
	

	
)

const (
	// DefaultEvictedBufferSize defines the default buffer size to store evicted key/val
	DefaultEvictedBufferSize = 16
)

// Cache is a thread-safe fixed size LRU cache.
type Cache[ comparable,  any] struct {
	lru         *simplelru.LRU[, ]
	evictedKeys []
	evictedVals []
	onEvictedCB func(k , v )
	lock        sync.RWMutex
}

// New creates an LRU of the given size.
func [ comparable,  any]( int) (*Cache[, ], error) {
	return NewWithEvict[, ](, nil)
}

// NewWithEvict constructs a fixed size cache with the given eviction
// callback.
func [ comparable,  any]( int,  func( ,  )) ( *Cache[, ],  error) {
	// create a cache with default settings
	 = &Cache[, ]{
		onEvictedCB: ,
	}
	if  != nil {
		.initEvictBuffers()
		 = .onEvicted
	}
	.lru,  = simplelru.NewLRU(, )
	return
}

func ( *Cache[, ]) () {
	.evictedKeys = make([], 0, DefaultEvictedBufferSize)
	.evictedVals = make([], 0, DefaultEvictedBufferSize)
}

// onEvicted save evicted key/val and sent in externally registered callback
// outside of critical section
func ( *Cache[, ]) ( ,  ) {
	.evictedKeys = append(.evictedKeys, )
	.evictedVals = append(.evictedVals, )
}

// Purge is used to completely clear the cache.
func ( *Cache[, ]) () {
	var  []
	var  []
	.lock.Lock()
	.lru.Purge()
	if .onEvictedCB != nil && len(.evictedKeys) > 0 {
		,  = .evictedKeys, .evictedVals
		.initEvictBuffers()
	}
	.lock.Unlock()
	// invoke callback outside of critical section
	if .onEvictedCB != nil {
		for  := 0;  < len(); ++ {
			.onEvictedCB([], [])
		}
	}
}

// Add adds a value to the cache. Returns true if an eviction occurred.
func ( *Cache[, ]) ( ,  ) ( bool) {
	var  
	var  
	.lock.Lock()
	 = .lru.Add(, )
	if .onEvictedCB != nil &&  {
		,  = .evictedKeys[0], .evictedVals[0]
		.evictedKeys, .evictedVals = .evictedKeys[:0], .evictedVals[:0]
	}
	.lock.Unlock()
	if .onEvictedCB != nil &&  {
		.onEvictedCB(, )
	}
	return
}

// Get looks up a key's value from the cache.
func ( *Cache[, ]) ( ) ( ,  bool) {
	.lock.Lock()
	,  = .lru.Get()
	.lock.Unlock()
	return , 
}

// Contains checks if a key is in the cache, without updating the
// recent-ness or deleting it for being stale.
func ( *Cache[, ]) ( ) bool {
	.lock.RLock()
	 := .lru.Contains()
	.lock.RUnlock()
	return 
}

// Peek returns the key value (or undefined if not found) without updating
// the "recently used"-ness of the key.
func ( *Cache[, ]) ( ) ( ,  bool) {
	.lock.RLock()
	,  = .lru.Peek()
	.lock.RUnlock()
	return , 
}

// ContainsOrAdd checks if a key is in the cache without updating the
// recent-ness or deleting it for being stale, and if not, adds the value.
// Returns whether found and whether an eviction occurred.
func ( *Cache[, ]) ( ,  ) (,  bool) {
	var  
	var  
	.lock.Lock()
	if .lru.Contains() {
		.lock.Unlock()
		return true, false
	}
	 = .lru.Add(, )
	if .onEvictedCB != nil &&  {
		,  = .evictedKeys[0], .evictedVals[0]
		.evictedKeys, .evictedVals = .evictedKeys[:0], .evictedVals[:0]
	}
	.lock.Unlock()
	if .onEvictedCB != nil &&  {
		.onEvictedCB(, )
	}
	return false, 
}

// PeekOrAdd checks if a key is in the cache without updating the
// recent-ness or deleting it for being stale, and if not, adds the value.
// Returns whether found and whether an eviction occurred.
func ( *Cache[, ]) ( ,  ) ( , ,  bool) {
	var  
	var  
	.lock.Lock()
	,  = .lru.Peek()
	if  {
		.lock.Unlock()
		return , true, false
	}
	 = .lru.Add(, )
	if .onEvictedCB != nil &&  {
		,  = .evictedKeys[0], .evictedVals[0]
		.evictedKeys, .evictedVals = .evictedKeys[:0], .evictedVals[:0]
	}
	.lock.Unlock()
	if .onEvictedCB != nil &&  {
		.onEvictedCB(, )
	}
	return
}

// Remove removes the provided key from the cache.
func ( *Cache[, ]) ( ) ( bool) {
	var  
	var  
	.lock.Lock()
	 = .lru.Remove()
	if .onEvictedCB != nil &&  {
		,  = .evictedKeys[0], .evictedVals[0]
		.evictedKeys, .evictedVals = .evictedKeys[:0], .evictedVals[:0]
	}
	.lock.Unlock()
	if .onEvictedCB != nil &&  {
		.onEvictedCB(, )
	}
	return
}

// Resize changes the cache size.
func ( *Cache[, ]) ( int) ( int) {
	var  []
	var  []
	.lock.Lock()
	 = .lru.Resize()
	if .onEvictedCB != nil &&  > 0 {
		,  = .evictedKeys, .evictedVals
		.initEvictBuffers()
	}
	.lock.Unlock()
	if .onEvictedCB != nil &&  > 0 {
		for  := 0;  < len(); ++ {
			.onEvictedCB([], [])
		}
	}
	return 
}

// RemoveOldest removes the oldest item from the cache.
func ( *Cache[, ]) () ( ,  ,  bool) {
	var  
	var  
	.lock.Lock()
	, ,  = .lru.RemoveOldest()
	if .onEvictedCB != nil &&  {
		,  = .evictedKeys[0], .evictedVals[0]
		.evictedKeys, .evictedVals = .evictedKeys[:0], .evictedVals[:0]
	}
	.lock.Unlock()
	if .onEvictedCB != nil &&  {
		.onEvictedCB(, )
	}
	return
}

// GetOldest returns the oldest entry
func ( *Cache[, ]) () ( ,  ,  bool) {
	.lock.RLock()
	, ,  = .lru.GetOldest()
	.lock.RUnlock()
	return
}

// Keys returns a slice of the keys in the cache, from oldest to newest.
func ( *Cache[, ]) () [] {
	.lock.RLock()
	 := .lru.Keys()
	.lock.RUnlock()
	return 
}

// Values returns a slice of the values in the cache, from oldest to newest.
func ( *Cache[, ]) () [] {
	.lock.RLock()
	 := .lru.Values()
	.lock.RUnlock()
	return 
}

// Len returns the number of items in the cache.
func ( *Cache[, ]) () int {
	.lock.RLock()
	 := .lru.Len()
	.lock.RUnlock()
	return 
}