using System;
using System.Collections.Generic;

namespace VMdemo.Simulation
{
    public readonly struct EvictionInfo
    {
        public EvictionInfo(bool hasEvicted, ulong evictedVpn, ulong evictedPfn)
        {
            HasEvicted = hasEvicted;
            EvictedVpn = evictedVpn;
            EvictedPfn = evictedPfn;
        }

        public bool HasEvicted { get; }
        public ulong EvictedVpn { get; }
        public ulong EvictedPfn { get; }

        public static EvictionInfo None => new EvictionInfo(false, 0UL, 0UL);
    }

    public readonly struct AllocationResult
    {
        public AllocationResult(ulong assignedPfn, bool wasAlreadyResident, EvictionInfo eviction)
        {
            AssignedPfn = assignedPfn;
            WasAlreadyResident = wasAlreadyResident;
            Eviction = eviction;
        }

        public ulong AssignedPfn { get; }
        public bool WasAlreadyResident { get; }
        public EvictionInfo Eviction { get; }
    }

    public class PhysicalMemoryManager
    {
        private readonly Queue<ResidentPage> _fifoQueue = new Queue<ResidentPage>();
        private readonly Dictionary<ulong, ulong> _residentByVpn = new Dictionary<ulong, ulong>();
        private ulong _nextFreeFrame;

        public PhysicalMemoryManager(ulong frameCount)
        {
            if (frameCount < 1UL)
            {
                throw new ArgumentOutOfRangeException(nameof(frameCount), "frameCount 必须 >= 1。");
            }

            FrameCount = frameCount;
            _nextFreeFrame = 0UL;
        }

        public ulong FrameCount { get; }
        public int ResidentCount => _residentByVpn.Count;

        public bool TryGetResidentPfn(ulong vpn, out ulong pfn)
        {
            return _residentByVpn.TryGetValue(vpn, out pfn);
        }

        public AllocationResult AllocateForVpn(ulong vpn)
        {
            if (_residentByVpn.TryGetValue(vpn, out var existingPfn))
            {
                return new AllocationResult(existingPfn, true, EvictionInfo.None);
            }

            EvictionInfo evictionInfo;
            ulong assignedPfn;

            if (_nextFreeFrame < FrameCount)
            {
                assignedPfn = _nextFreeFrame;
                _nextFreeFrame++;
                evictionInfo = EvictionInfo.None;
            }
            else
            {
                if (_fifoQueue.Count == 0)
                {
                    throw new InvalidOperationException("内存已满但 FIFO 队列为空，状态异常。");
                }

                var victim = _fifoQueue.Dequeue();
                if (!_residentByVpn.Remove(victim.Vpn))
                {
                    throw new InvalidOperationException("FIFO 淘汰项未出现在驻留映射中，状态异常。");
                }

                assignedPfn = victim.Pfn;
                evictionInfo = new EvictionInfo(true, victim.Vpn, victim.Pfn);
            }

            _residentByVpn[vpn] = assignedPfn;
            _fifoQueue.Enqueue(new ResidentPage(vpn, assignedPfn));
            return new AllocationResult(assignedPfn, false, evictionInfo);
        }

        public IReadOnlyList<ulong> GetFifoVpnOrder()
        {
            var order = new List<ulong>(_fifoQueue.Count);
            foreach (var page in _fifoQueue)
            {
                order.Add(page.Vpn);
            }

            return order;
        }

        public string FormatFifoQueue()
        {
            var order = GetFifoVpnOrder();
            if (order.Count == 0)
            {
                return "FIFO=[]";
            }

            return $"FIFO=[{string.Join(",", order)}]";
        }

        private readonly struct ResidentPage
        {
            public ResidentPage(ulong vpn, ulong pfn)
            {
                Vpn = vpn;
                Pfn = pfn;
            }

            public ulong Vpn { get; }
            public ulong Pfn { get; }
        }
    }
}
