using System;
using System.Collections.Generic;
using System.Xml.Linq;
namespace DiscordTranslator.Bot.Logic
{
///
/// A Least Recently Used cache with optional forced expiry/eviction.
///
public class LruCache where K : notnull where V : notnull
{
private readonly int _capacity;
private readonly Dictionary> _map = [];
private readonly LinkedList _lru = new();
private readonly Lock _sync = new();
public int Count
{
get { lock (_sync) { return _lru.Count; } }
}
public LruCache(int capacity)
{
if (capacity <= 0) throw new ArgumentOutOfRangeException(nameof(capacity), "Capacity must be positive.");
_capacity = capacity;
}
///
/// Try to get a value by key and cast it to T (where T : V).
/// Returns true and sets 'val' when found and castable; otherwise false and val = default.
///
public bool TryGetValue(K key, out T? val) where T : V
{
lock (_sync)
{
if (_map.TryGetValue(key, out var node))
{
// Move to back (most recently used)
_lru.Remove(node);
_lru.AddLast(node);
if (node.Value.Value is T t)
{
val = t;
return true;
}
}
val = default;
return false;
}
}
public void Set(K key, V value)
{
lock (_sync)
{
if (_map.TryGetValue(key, out var existing))
{
existing.Value = existing.Value with { Value = value };
// Refresh LRU position
_lru.Remove(existing);
_lru.AddLast(existing);
}
else
{
if (_map.Count >= _capacity)
RemoveFirst(); // evict LRU
var item = new LRUCacheItem(key, value);
var node = new LinkedListNode(item);
_lru.AddLast(node);
_map[key] = node;
}
}
}
public void Remove(K key)
{
lock (_sync)
{
if (_map.TryGetValue(key, out var node))
{
_lru.Remove(node);
_map.Remove(key);
if (node.Value is IDisposable dispVal)
{
dispVal.Dispose();
}
}
}
}
private void RemoveFirst()
{
// Evict least recently used (front)
if (_lru.First is { } first)
{
_lru.RemoveFirst();
// Remove from map using the evicted key
_map.Remove(first.Value.Key);
if (first.Value is IDisposable dispVal)
{
dispVal.Dispose();
}
}
// If list was empty, nothing to evict (shouldn't happen with capacity checks)
}
internal sealed record LRUCacheItem(K Key, V Value)
{
public override string ToString() => $"{Key} => {Value}";
}
}
}