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

package ristretto

import (
	
	
)

var (
	// TODO: find the optimal value or make it configurable.
	bucketDurationSecs = int64(5)
)

func storageBucket( time.Time) int64 {
	return (.Unix() / bucketDurationSecs) + 1
}

func cleanupBucket( time.Time) int64 {
	// The bucket to cleanup is always behind the storage bucket by one so that
	// no elements in that bucket (which might not have expired yet) are deleted.
	return storageBucket() - 1
}

// bucket type is a map of key to conflict.
type bucket map[uint64]uint64

// expirationMap is a map of bucket number to the corresponding bucket.
type expirationMap[ any] struct {
	sync.RWMutex
	buckets              map[int64]bucket
	lastCleanedBucketNum int64
}

func newExpirationMap[ any]() *expirationMap[] {
	return &expirationMap[]{
		buckets:              make(map[int64]bucket),
		lastCleanedBucketNum: cleanupBucket(time.Now()),
	}
}

func ( *expirationMap[]) (,  uint64,  time.Time) {
	if  == nil {
		return
	}

	// Items that don't expire don't need to be in the expiration map.
	if .IsZero() {
		return
	}

	 := storageBucket()
	.Lock()
	defer .Unlock()

	,  := .buckets[]
	if ! {
		 = make(bucket)
		.buckets[] = 
	}
	[] = 
}

func ( *expirationMap[]) (,  uint64, ,  time.Time) {
	if  == nil {
		return
	}

	.Lock()
	defer .Unlock()

	 := storageBucket()
	,  := .buckets[]
	if  {
		delete(, )
	}

	// Items that don't expire don't need to be in the expiration map.
	if .IsZero() {
		return
	}

	 := storageBucket()
	,  := .buckets[]
	if ! {
		 = make(bucket)
		.buckets[] = 
	}
	[] = 
}

func ( *expirationMap[]) ( uint64,  time.Time) {
	if  == nil {
		return
	}

	 := storageBucket()
	.Lock()
	defer .Unlock()
	,  := .buckets[]
	if ! {
		return
	}
	delete(.buckets[], )
}

// cleanup removes all the items in the bucket that was just completed. It deletes
// those items from the store, and calls the onEvict function on those items.
// This function is meant to be called periodically.
func ( *expirationMap[]) ( store[],  *defaultPolicy[],  func( *Item[])) int {
	if  == nil {
		return 0
	}

	.Lock()
	 := time.Now()
	 := cleanupBucket()
	// Clean up all buckets up to and including currentBucketNum, starting from
	// (but not including) the last one that was cleaned up
	var  []bucket
	for  := .lastCleanedBucketNum + 1;  <= ; ++ {
		// With an empty bucket, we don't need to add it to the Clean list
		if  := .buckets[];  != nil {
			 = append(, )
		}
		delete(.buckets, )
	}
	.lastCleanedBucketNum = 
	.Unlock()

	for ,  := range  {
		for ,  := range  {
			 := .Expiration()
			// Sanity check. Verify that the store agrees that this key is expired.
			if .After() {
				continue
			}

			 := .Cost()
			.Del()
			,  := .Del(, )

			if  != nil {
				(&Item[]{Key: ,
					Conflict:   ,
					Value:      ,
					Cost:       ,
					Expiration: ,
				})
			}
		}
	}

	 := len()

	return 
}

// clear clears the expirationMap, the caller is responsible for properly
// evicting the referenced items
func ( *expirationMap[]) () {
	if  == nil {
		return
	}

	.Lock()
	.buckets = make(map[int64]bucket)
	.lastCleanedBucketNum = cleanupBucket(time.Now())
	.Unlock()
}