using System;

namespace VMdemo.Simulation
{
    public readonly struct MemoryAccessResult
    {
        public MemoryAccessResult(ulong pfn, bool pageTableHit, bool pageFault, EvictionInfo eviction)
        {
            Pfn = pfn;
            PageTableHit = pageTableHit;
            PageFault = pageFault;
            Eviction = eviction;
        }

        public ulong Pfn { get; }
        public bool PageTableHit { get; }
        public bool PageFault { get; }
        public EvictionInfo Eviction { get; }
    }

    public static class MemoryAccessResolver
    {
        public static MemoryAccessResult Resolve(
            ulong vpn,
            ulong l1Index,
            ulong l2Index,
            TwoLevelPageTable pageTable,
            PhysicalMemoryManager physicalMemoryManager,
            TlbCache tlbCache)
        {
            if (pageTable == null)
            {
                throw new ArgumentNullException(nameof(pageTable));
            }

            if (physicalMemoryManager == null)
            {
                throw new ArgumentNullException(nameof(physicalMemoryManager));
            }

            if (tlbCache == null)
            {
                throw new ArgumentNullException(nameof(tlbCache));
            }

            if (pageTable.TryGetPresentPfn(l1Index, l2Index, out var pfn))
            {
                tlbCache.InsertOrUpdate(vpn, pfn);
                return new MemoryAccessResult(pfn, true, false, EvictionInfo.None);
            }

            var allocation = physicalMemoryManager.AllocateForVpn(vpn);
            if (allocation.Eviction.HasEvicted)
            {
                pageTable.GetIndicesFromVpn(
                    allocation.Eviction.EvictedVpn,
                    out var evictedL1Index,
                    out var evictedL2Index);

                pageTable.MarkNotPresent(evictedL1Index, evictedL2Index);
                tlbCache.Remove(allocation.Eviction.EvictedVpn);
            }

            pageTable.SetPresent(l1Index, l2Index, allocation.AssignedPfn);
            tlbCache.InsertOrUpdate(vpn, allocation.AssignedPfn);

            return new MemoryAccessResult(
                allocation.AssignedPfn,
                pageTableHit: false,
                pageFault: true,
                allocation.Eviction);
        }
    }
}
