using System.Collections.Generic;
using NUnit.Framework;
using VMdemo.Core;
using VMdemo.Simulation;

namespace VMdemo.Tests.EditMode
{
    public class Step5TranslatorEngineTests
    {
        [Test]
        public void StepOnce_FirstAccess_VisitsAllStagesInOrder()
        {
            var engine = CreateEngine(frameCountMb: 1, tlbEntries: 4, seed: 77);
            engine.SetNextVirtualAddress(0x0000_1234UL);

            var visited = new List<TranslationStep>();
            bool completed;
            do
            {
                visited.Add(engine.CurrentStep);
                completed = engine.StepOnce();
            } while (!completed);

            var expected = new[]
            {
                TranslationStep.GenerateVA,
                TranslationStep.SplitVA,
                TranslationStep.LookupTLB,
                TranslationStep.LookupPageTable,
                TranslationStep.HandlePageFault,
                TranslationStep.ComposePA,
                TranslationStep.Finalize
            };

            CollectionAssert.AreEqual(expected, visited);
            Assert.That(engine.State.CurrentRound, Is.EqualTo(1));
            Assert.That(engine.CurrentStep, Is.EqualTo(TranslationStep.GenerateVA));
        }

        [Test]
        public void StepOnce_TlbHitPath_SkipsPageTableAndFaultStages()
        {
            var engine = CreateEngine(frameCountMb: 1, tlbEntries: 4, seed: 88);
            const ulong va = 0x0000_4567UL;

            engine.RunOneAccess(va); // warmup: make resident + populate tlb

            engine.SetNextVirtualAddress(va);
            var visited = new List<TranslationStep>();
            bool completed;
            do
            {
                visited.Add(engine.CurrentStep);
                completed = engine.StepOnce();
            } while (!completed);

            var expected = new[]
            {
                TranslationStep.GenerateVA,
                TranslationStep.SplitVA,
                TranslationStep.LookupTLB,
                TranslationStep.ComposePA,
                TranslationStep.Finalize
            };

            CollectionAssert.AreEqual(expected, visited);
            Assert.IsTrue(engine.State.IsTlbHit);
            Assert.IsFalse(engine.State.IsPageFault);
        }

        [Test]
        public void StepOnce_PageTableHitPath_WritesBackToTlb()
        {
            var engine = CreateEngine(frameCountMb: 1, tlbEntries: 2, seed: 99);
            const ulong va = 0x0000_89ABUL;
            var parts = AddressTranslatorUtils.SplitVirtualAddress(va, 32, engine.OffsetBits);

            engine.PageTable.SetPresent(parts.L1Index, parts.L2Index, pfn: 3UL);
            engine.SetNextVirtualAddress(va);

            var visited = new List<TranslationStep>();
            bool completed;
            do
            {
                visited.Add(engine.CurrentStep);
                completed = engine.StepOnce();
            } while (!completed);

            var expected = new[]
            {
                TranslationStep.GenerateVA,
                TranslationStep.SplitVA,
                TranslationStep.LookupTLB,
                TranslationStep.LookupPageTable,
                TranslationStep.ComposePA,
                TranslationStep.Finalize
            };

            CollectionAssert.AreEqual(expected, visited);
            Assert.IsFalse(engine.State.IsTlbHit);
            Assert.IsTrue(engine.State.IsPageTableHit);
            Assert.IsFalse(engine.State.IsPageFault);
            Assert.IsTrue(engine.TlbCache.TryLookup(parts.Vpn, out var cachedPfn));
            Assert.That(cachedPfn, Is.EqualTo(3UL));
        }

        [Test]
        public void RunOneAccess_WhenMemoryFull_ProducesEvictionInfoAndSyncsState()
        {
            var config = new SimulationConfig
            {
                vaBits = 32,
                pageSizeKB = 1024, // 1MB per page
                physicalMemoryMB = 1, // 1 frame total
                tlbEntries = 4,
                accessCount = 10,
                pageFaultPenalty = 100
            };
            var engine = new TranslatorEngine(config, seed: 11);

            var first = engine.RunOneAccess(0x0000_0123UL);
            var second = engine.RunOneAccess(0x0010_0123UL); // different VPN

            Assert.IsTrue(first.PageFault);
            Assert.IsTrue(second.PageFault);
            Assert.IsTrue(second.Eviction.HasEvicted);
            Assert.That(second.Eviction.EvictedVpn, Is.EqualTo(first.AddressParts.Vpn));
            Assert.IsFalse(engine.TlbCache.TryLookup(first.AddressParts.Vpn, out _));
            Assert.That(engine.LastEviction.EvictedVpn, Is.EqualTo(first.AddressParts.Vpn));
        }

        [Test]
        public void RunOneAccess_ForcedVa_MatchesStepByStepOutcome()
        {
            const ulong va = 0x00AB_CDEFUL;

            var engineA = CreateEngine(frameCountMb: 1, tlbEntries: 4, seed: 123);
            var result = engineA.RunOneAccess(va);

            var engineB = CreateEngine(frameCountMb: 1, tlbEntries: 4, seed: 123);
            engineB.SetNextVirtualAddress(va);
            while (!engineB.StepOnce())
            {
            }

            Assert.That(engineB.State.CurrentVirtualAddress, Is.EqualTo(result.VirtualAddress));
            Assert.That(engineB.State.CurrentAddressParts.Vpn, Is.EqualTo(result.AddressParts.Vpn));
            Assert.That(engineB.State.CurrentPfn, Is.EqualTo(result.Pfn));
            Assert.That(engineB.State.CurrentPhysicalAddress, Is.EqualTo(result.PhysicalAddress));
            Assert.That(engineB.State.IsPageFault, Is.EqualTo(result.PageFault));
            Assert.That(engineB.State.IsPageTableHit, Is.EqualTo(result.PageTableHit));
            Assert.That(engineB.State.IsTlbHit, Is.EqualTo(result.TlbHit));
            Assert.That(engineB.State.CurrentRound, Is.EqualTo(result.Round));
        }

        [Test]
        public void Reset_ClearsStateAndRuntimeCaches()
        {
            var engine = CreateEngine(frameCountMb: 1, tlbEntries: 4, seed: 222);
            engine.RunOneAccess(0x0000_1000UL);

            Assert.That(engine.State.CurrentRound, Is.EqualTo(1));
            Assert.That(engine.TlbCache.Count, Is.GreaterThan(0));
            Assert.That(engine.PhysicalMemoryManager.ResidentCount, Is.GreaterThan(0));

            engine.Reset();

            Assert.That(engine.State.CurrentRound, Is.EqualTo(0));
            Assert.That(engine.State.CurrentStep, Is.EqualTo(TranslationStep.GenerateVA));
            Assert.That(engine.State.CurrentVirtualAddress, Is.EqualTo(0UL));
            Assert.That(engine.State.HasCurrentPfn, Is.False);
            Assert.That(engine.TlbCache.Count, Is.EqualTo(0));
            Assert.That(engine.PhysicalMemoryManager.ResidentCount, Is.EqualTo(0));
            Assert.That(engine.LastEviction.HasEvicted, Is.False);
        }

        private static TranslatorEngine CreateEngine(int frameCountMb, int tlbEntries, int seed)
        {
            var config = new SimulationConfig
            {
                vaBits = 32,
                pageSizeKB = 4,
                physicalMemoryMB = frameCountMb,
                tlbEntries = tlbEntries,
                accessCount = 10,
                pageFaultPenalty = 100
            };

            return new TranslatorEngine(config, seed);
        }
    }
}
