using System;
using System.Collections.Generic;

namespace VMdemo.Simulation
{
    public class TlbCache
    {
        private readonly Dictionary<ulong, LinkedListNode<TlbEntry>> _index;
        private readonly LinkedList<TlbEntry> _lruList;

        public TlbCache(int capacity)
        {
            if (capacity <= 0)
            {
                throw new ArgumentOutOfRangeException(nameof(capacity), "TLB 容量必须是正整数。");
            }

            Capacity = capacity;
            _index = new Dictionary<ulong, LinkedListNode<TlbEntry>>(capacity);
            _lruList = new LinkedList<TlbEntry>();
        }

        public int Capacity { get; }
        public int Count => _index.Count;
        public int HitCount { get; private set; }
        public int MissCount { get; private set; }

        public bool Lookup(ulong vpn, out ulong pfn)
        {
            return TryLookup(vpn, out pfn);
        }

        public bool TryLookup(ulong vpn, out ulong pfn)
        {
            if (_index.TryGetValue(vpn, out var node) && node.Value.IsValid)
            {
                MoveToMostRecent(node);
                pfn = node.Value.Pfn;
                HitCount++;
                return true;
            }

            pfn = 0UL;
            MissCount++;
            return false;
        }

        public void InsertOrUpdate(ulong vpn, ulong pfn)
        {
            if (_index.TryGetValue(vpn, out var existingNode))
            {
                existingNode.Value = existingNode.Value.WithPfn(pfn);
                MoveToMostRecent(existingNode);
                return;
            }

            var newNode = new LinkedListNode<TlbEntry>(new TlbEntry(vpn, pfn));
            _lruList.AddFirst(newNode);
            _index[vpn] = newNode;

            if (_index.Count > Capacity)
            {
                EvictLeastRecent();
            }
        }

        public bool Remove(ulong vpn)
        {
            if (!_index.TryGetValue(vpn, out var node))
            {
                return false;
            }

            _lruList.Remove(node);
            _index.Remove(vpn);
            return true;
        }

        public void Clear()
        {
            _lruList.Clear();
            _index.Clear();
            HitCount = 0;
            MissCount = 0;
        }

        public IReadOnlyList<TlbEntry> GetEntriesMostRecentFirst()
        {
            var snapshot = new List<TlbEntry>(_index.Count);
            for (var node = _lruList.First; node != null; node = node.Next)
            {
                snapshot.Add(node.Value);
            }

            return snapshot;
        }

        private void MoveToMostRecent(LinkedListNode<TlbEntry> node)
        {
            if (node.List != _lruList || _lruList.First == node)
            {
                return;
            }

            _lruList.Remove(node);
            _lruList.AddFirst(node);
        }

        private void EvictLeastRecent()
        {
            var leastRecentNode = _lruList.Last;
            if (leastRecentNode == null)
            {
                return;
            }

            _lruList.RemoveLast();
            _index.Remove(leastRecentNode.Value.Vpn);
        }
    }
}
