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

package lru

import (
	
	

	
)

const (
	// Default2QRecentRatio is the ratio of the 2Q cache dedicated
	// to recently added entries that have only been accessed once.
	Default2QRecentRatio = 0.25

	// Default2QGhostEntries is the default ratio of ghost
	// entries kept to track entries recently evicted
	Default2QGhostEntries = 0.50
)

// TwoQueueCache is a thread-safe fixed size 2Q cache.
// 2Q is an enhancement over the standard LRU cache
// in that it tracks both frequently and recently used
// entries separately. This avoids a burst in access to new
// entries from evicting frequently used entries. It adds some
// additional tracking overhead to the standard LRU cache, and is
// computationally about 2x the cost, and adds some metadata over
// head. The ARCCache is similar, but does not require setting any
// parameters.
type TwoQueueCache[ comparable,  any] struct {
	size        int
	recentSize  int
	recentRatio float64
	ghostRatio  float64

	recent      simplelru.LRUCache[, ]
	frequent    simplelru.LRUCache[, ]
	recentEvict simplelru.LRUCache[, struct{}]
	lock        sync.RWMutex
}

// New2Q creates a new TwoQueueCache using the default
// values for the parameters.
func [ comparable,  any]( int) (*TwoQueueCache[, ], error) {
	return New2QParams[, ](, Default2QRecentRatio, Default2QGhostEntries)
}

// New2QParams creates a new TwoQueueCache using the provided
// parameter values.
func [ comparable,  any]( int, ,  float64) (*TwoQueueCache[, ], error) {
	if  <= 0 {
		return nil, errors.New("invalid size")
	}
	if  < 0.0 ||  > 1.0 {
		return nil, errors.New("invalid recent ratio")
	}
	if  < 0.0 ||  > 1.0 {
		return nil, errors.New("invalid ghost ratio")
	}

	// Determine the sub-sizes
	 := int(float64() * )
	 := int(float64() * )

	// Allocate the LRUs
	,  := simplelru.NewLRU[, ](, nil)
	if  != nil {
		return nil, 
	}
	,  := simplelru.NewLRU[, ](, nil)
	if  != nil {
		return nil, 
	}
	,  := simplelru.NewLRU[, struct{}](, nil)
	if  != nil {
		return nil, 
	}

	// Initialize the cache
	 := &TwoQueueCache[, ]{
		size:        ,
		recentSize:  ,
		recentRatio: ,
		ghostRatio:  ,
		recent:      ,
		frequent:    ,
		recentEvict: ,
	}
	return , nil
}

// Get looks up a key's value from the cache.
func ( *TwoQueueCache[, ]) ( ) ( ,  bool) {
	.lock.Lock()
	defer .lock.Unlock()

	// Check if this is a frequent value
	if ,  := .frequent.Get();  {
		return , 
	}

	// If the value is contained in recent, then we
	// promote it to frequent
	if ,  := .recent.Peek();  {
		.recent.Remove()
		.frequent.Add(, )
		return , 
	}

	// No hit
	return
}

// Add adds a value to the cache.
func ( *TwoQueueCache[, ]) ( ,  ) {
	.lock.Lock()
	defer .lock.Unlock()

	// Check if the value is frequently used already,
	// and just update the value
	if .frequent.Contains() {
		.frequent.Add(, )
		return
	}

	// Check if the value is recently used, and promote
	// the value into the frequent list
	if .recent.Contains() {
		.recent.Remove()
		.frequent.Add(, )
		return
	}

	// If the value was recently evicted, add it to the
	// frequently used list
	if .recentEvict.Contains() {
		.ensureSpace(true)
		.recentEvict.Remove()
		.frequent.Add(, )
		return
	}

	// Add to the recently seen list
	.ensureSpace(false)
	.recent.Add(, )
}

// ensureSpace is used to ensure we have space in the cache
func ( *TwoQueueCache[, ]) ( bool) {
	// If we have space, nothing to do
	 := .recent.Len()
	 := .frequent.Len()
	if + < .size {
		return
	}

	// If the recent buffer is larger than
	// the target, evict from there
	if  > 0 && ( > .recentSize || ( == .recentSize && !)) {
		, ,  := .recent.RemoveOldest()
		.recentEvict.Add(, struct{}{})
		return
	}

	// Remove from the frequent list otherwise
	.frequent.RemoveOldest()
}

// Len returns the number of items in the cache.
func ( *TwoQueueCache[, ]) () int {
	.lock.RLock()
	defer .lock.RUnlock()
	return .recent.Len() + .frequent.Len()
}

// Resize changes the cache size.
func ( *TwoQueueCache[, ]) ( int) ( int) {
	.lock.Lock()
	defer .lock.Unlock()

	// Recalculate the sub-sizes
	 := int(float64() * .recentRatio)
	 := int(float64() * .ghostRatio)
	.size = 
	.recentSize = 

	// ensureSpace
	 := .recent.Len() + .frequent.Len() - 
	if  < 0 {
		 = 0
	}
	for  := 0;  < ; ++ {
		.ensureSpace(true)
	}

	// Reallocate the LRUs
	.recent.Resize()
	.frequent.Resize()
	.recentEvict.Resize()

	return 
}

// Keys returns a slice of the keys in the cache.
// The frequently used keys are first in the returned slice.
func ( *TwoQueueCache[, ]) () [] {
	.lock.RLock()
	defer .lock.RUnlock()
	 := .frequent.Keys()
	 := .recent.Keys()
	return append(, ...)
}

// Values returns a slice of the values in the cache.
// The frequently used values are first in the returned slice.
func ( *TwoQueueCache[, ]) () [] {
	.lock.RLock()
	defer .lock.RUnlock()
	 := .frequent.Values()
	 := .recent.Values()
	return append(, ...)
}

// Remove removes the provided key from the cache.
func ( *TwoQueueCache[, ]) ( ) {
	.lock.Lock()
	defer .lock.Unlock()
	if .frequent.Remove() {
		return
	}
	if .recent.Remove() {
		return
	}
	if .recentEvict.Remove() {
		return
	}
}

// Purge is used to completely clear the cache.
func ( *TwoQueueCache[, ]) () {
	.lock.Lock()
	defer .lock.Unlock()
	.recent.Purge()
	.frequent.Purge()
	.recentEvict.Purge()
}

// Contains is used to check if the cache contains a key
// without updating recency or frequency.
func ( *TwoQueueCache[, ]) ( ) bool {
	.lock.RLock()
	defer .lock.RUnlock()
	return .frequent.Contains() || .recent.Contains()
}

// Peek is used to inspect the cache value of a key
// without updating recency or frequency.
func ( *TwoQueueCache[, ]) ( ) ( ,  bool) {
	.lock.RLock()
	defer .lock.RUnlock()
	if ,  := .frequent.Peek();  {
		return , 
	}
	return .recent.Peek()
}