using NUnit.Framework;
using VMdemo.Simulation;

namespace VMdemo.Tests.EditMode
{
    public class Step3TlbCacheTests
    {
        [Test]
        public void Constructor_CapacityLessThanOne_Throws()
        {
            Assert.Throws<System.ArgumentOutOfRangeException>(() => new TlbCache(0));
            Assert.Throws<System.ArgumentOutOfRangeException>(() => new TlbCache(-1));
        }

        [Test]
        public void TryLookup_Miss_ReturnsFalseAndCountsMiss()
        {
            var cache = new TlbCache(2);

            var hit = cache.TryLookup(10UL, out var pfn);

            Assert.IsFalse(hit);
            Assert.That(pfn, Is.EqualTo(0UL));
            Assert.That(cache.HitCount, Is.EqualTo(0));
            Assert.That(cache.MissCount, Is.EqualTo(1));
        }

        [Test]
        public void InsertOrUpdate_AndLookup_HitReturnsMappedPfn()
        {
            var cache = new TlbCache(2);
            cache.InsertOrUpdate(10UL, 100UL);

            var hit = cache.TryLookup(10UL, out var pfn);

            Assert.IsTrue(hit);
            Assert.That(pfn, Is.EqualTo(100UL));
            Assert.That(cache.HitCount, Is.EqualTo(1));
            Assert.That(cache.MissCount, Is.EqualTo(0));
        }

        [Test]
        public void InsertOrUpdate_ExistingEntry_UpdatesPfnAndMovesToMostRecent()
        {
            var cache = new TlbCache(3);
            cache.InsertOrUpdate(1UL, 11UL);
            cache.InsertOrUpdate(2UL, 22UL);
            cache.InsertOrUpdate(3UL, 33UL);

            cache.InsertOrUpdate(1UL, 111UL);

            var entries = cache.GetEntriesMostRecentFirst();

            Assert.That(entries.Count, Is.EqualTo(3));
            Assert.That(entries[0].Vpn, Is.EqualTo(1UL));
            Assert.That(entries[0].Pfn, Is.EqualTo(111UL));
            Assert.That(entries[1].Vpn, Is.EqualTo(3UL));
            Assert.That(entries[2].Vpn, Is.EqualTo(2UL));
        }

        [Test]
        public void OverCapacity_EvictsLeastRecentlyUsedEntry()
        {
            var cache = new TlbCache(3);
            cache.InsertOrUpdate(1UL, 11UL);
            cache.InsertOrUpdate(2UL, 22UL);
            cache.InsertOrUpdate(3UL, 33UL);

            cache.TryLookup(1UL, out _); // refresh 1 as MRU
            cache.InsertOrUpdate(4UL, 44UL); // should evict 2

            Assert.That(cache.Count, Is.EqualTo(3));
            Assert.IsFalse(cache.TryLookup(2UL, out _));
            Assert.IsTrue(cache.TryLookup(1UL, out var pfn1));
            Assert.That(pfn1, Is.EqualTo(11UL));

            var entries = cache.GetEntriesMostRecentFirst();
            Assert.That(entries[0].Vpn, Is.EqualTo(1UL));
            Assert.That(entries[1].Vpn, Is.EqualTo(4UL));
            Assert.That(entries[2].Vpn, Is.EqualTo(3UL));
        }

        [Test]
        public void Remove_ExistingAndMissingKeys_BehaveAsExpected()
        {
            var cache = new TlbCache(2);
            cache.InsertOrUpdate(5UL, 55UL);

            var removedFirst = cache.Remove(5UL);
            var removedSecond = cache.Remove(5UL);

            Assert.IsTrue(removedFirst);
            Assert.IsFalse(removedSecond);
            Assert.That(cache.Count, Is.EqualTo(0));
        }

        [Test]
        public void Clear_ResetsEntriesAndCounters()
        {
            var cache = new TlbCache(2);
            cache.InsertOrUpdate(1UL, 10UL);
            cache.InsertOrUpdate(2UL, 20UL);
            cache.TryLookup(1UL, out _);
            cache.TryLookup(99UL, out _);

            cache.Clear();

            Assert.That(cache.Count, Is.EqualTo(0));
            Assert.That(cache.HitCount, Is.EqualTo(0));
            Assert.That(cache.MissCount, Is.EqualTo(0));
            Assert.That(cache.GetEntriesMostRecentFirst().Count, Is.EqualTo(0));
        }

        [Test]
        public void ContinuousLookup_TracksHitAndMissCountsAccurately()
        {
            var cache = new TlbCache(2);
            cache.InsertOrUpdate(1UL, 10UL);
            cache.InsertOrUpdate(2UL, 20UL);

            cache.TryLookup(1UL, out _);   // hit
            cache.TryLookup(3UL, out _);   // miss
            cache.TryLookup(2UL, out _);   // hit
            cache.TryLookup(3UL, out _);   // miss

            Assert.That(cache.HitCount, Is.EqualTo(2));
            Assert.That(cache.MissCount, Is.EqualTo(2));
        }
    }
}
