using System;
using VMdemo.Core;

namespace VMdemo.Simulation
{
    public enum TranslationStep
    {
        GenerateVA = 0,
        SplitVA = 1,
        LookupTLB = 2,
        LookupPageTable = 3,
        HandlePageFault = 4,
        ComposePA = 5,
        Finalize = 6
    }

    public readonly struct AccessExecutionResult
    {
        public AccessExecutionResult(
            int round,
            ulong virtualAddress,
            AddressParts addressParts,
            ulong pfn,
            ulong physicalAddress,
            bool tlbHit,
            bool pageTableHit,
            bool pageFault,
            EvictionInfo eviction)
        {
            Round = round;
            VirtualAddress = virtualAddress;
            AddressParts = addressParts;
            Pfn = pfn;
            PhysicalAddress = physicalAddress;
            TlbHit = tlbHit;
            PageTableHit = pageTableHit;
            PageFault = pageFault;
            Eviction = eviction;
        }

        public int Round { get; }
        public ulong VirtualAddress { get; }
        public AddressParts AddressParts { get; }
        public ulong Pfn { get; }
        public ulong PhysicalAddress { get; }
        public bool TlbHit { get; }
        public bool PageTableHit { get; }
        public bool PageFault { get; }
        public EvictionInfo Eviction { get; }
    }

    public class TranslatorEngine
    {
        private readonly SimulationConfig _config;
        private readonly ConfigValidator.DerivedConfig _derivedConfig;
        private readonly int? _seed;

        private AddressGenerator _addressGenerator;
        private TlbCache _tlbCache;
        private TwoLevelPageTable _pageTable;
        private PhysicalMemoryManager _physicalMemoryManager;

        private bool _hasPendingForcedVirtualAddress;
        private ulong _pendingForcedVirtualAddress;
        private EvictionInfo _lastEviction;

        public TranslatorEngine(SimulationConfig config, int? seed = null)
        {
            if (!ConfigValidator.TryValidateAndBuildDerived(config, out var derived, out var errorMessage))
            {
                throw new ArgumentException(errorMessage, nameof(config));
            }

            _config = config;
            _derivedConfig = derived;
            _seed = seed;
            State = new SimulationState();

            AddressTranslatorUtils.GetPageTableBitLayout(derived.VpnBits, out var l1Bits, out var l2Bits);
            L1Bits = l1Bits;
            L2Bits = l2Bits;

            _addressGenerator = new AddressGenerator(_config.vaBits, _seed);
            _tlbCache = new TlbCache(_config.tlbEntries);
            _pageTable = new TwoLevelPageTable(L1Bits, L2Bits);
            _physicalMemoryManager = new PhysicalMemoryManager(_derivedConfig.FrameCount);

            Reset();
        }

        public SimulationState State { get; }
        public TranslationStep CurrentStep => State.CurrentStep;
        public int OffsetBits => _derivedConfig.OffsetBits;
        public int L1Bits { get; }
        public int L2Bits { get; }
        public ulong MaxVirtualAddress => _addressGenerator.MaxVirtualAddress;
        public TlbCache TlbCache => _tlbCache;
        public TwoLevelPageTable PageTable => _pageTable;
        public PhysicalMemoryManager PhysicalMemoryManager => _physicalMemoryManager;
        public EvictionInfo LastEviction => _lastEviction;

        public void Reset()
        {
            State.Reset();
            _lastEviction = EvictionInfo.None;
            _hasPendingForcedVirtualAddress = false;
            _pendingForcedVirtualAddress = 0UL;

            _addressGenerator = new AddressGenerator(_config.vaBits, _seed);
            _tlbCache = new TlbCache(_config.tlbEntries);
            _pageTable = new TwoLevelPageTable(L1Bits, L2Bits);
            _physicalMemoryManager = new PhysicalMemoryManager(_derivedConfig.FrameCount);
        }

        public void SetNextVirtualAddress(ulong virtualAddress)
        {
            EnsureVirtualAddressInRange(virtualAddress);
            _pendingForcedVirtualAddress = virtualAddress;
            _hasPendingForcedVirtualAddress = true;
        }

        public bool StepOnce()
        {
            switch (State.CurrentStep)
            {
                case TranslationStep.GenerateVA:
                    ExecuteGenerateVa();
                    return false;
                case TranslationStep.SplitVA:
                    ExecuteSplitVa();
                    return false;
                case TranslationStep.LookupTLB:
                    ExecuteLookupTlb();
                    return false;
                case TranslationStep.LookupPageTable:
                    ExecuteLookupPageTable();
                    return false;
                case TranslationStep.HandlePageFault:
                    ExecuteHandlePageFault();
                    return false;
                case TranslationStep.ComposePA:
                    ExecuteComposePa();
                    return false;
                case TranslationStep.Finalize:
                    ExecuteFinalize();
                    return true;
                default:
                    throw new InvalidOperationException($"未知翻译步骤：{State.CurrentStep}");
            }
        }

        public AccessExecutionResult RunOneAccess(ulong? forcedVirtualAddress = null)
        {
            if (State.CurrentStep != TranslationStep.GenerateVA)
            {
                throw new InvalidOperationException("RunOneAccess 只能在 GenerateVA 起始步骤调用。");
            }

            if (forcedVirtualAddress.HasValue)
            {
                SetNextVirtualAddress(forcedVirtualAddress.Value);
            }

            while (!StepOnce())
            {
            }

            return new AccessExecutionResult(
                State.CurrentRound,
                State.CurrentVirtualAddress,
                State.CurrentAddressParts,
                State.CurrentPfn,
                State.CurrentPhysicalAddress,
                State.IsTlbHit,
                State.IsPageTableHit,
                State.IsPageFault,
                _lastEviction);
        }

        private void ExecuteGenerateVa()
        {
            State.IsTlbHit = false;
            State.IsPageTableHit = false;
            State.IsPageFault = false;
            State.CurrentCost = 0;
            State.CurrentAddressParts = AddressParts.Empty;
            State.CurrentPhysicalAddress = 0UL;
            State.CurrentPfn = 0UL;
            State.HasCurrentPfn = false;
            _lastEviction = EvictionInfo.None;

            if (_hasPendingForcedVirtualAddress)
            {
                State.CurrentVirtualAddress = _pendingForcedVirtualAddress;
                _hasPendingForcedVirtualAddress = false;
                _pendingForcedVirtualAddress = 0UL;
            }
            else
            {
                State.CurrentVirtualAddress = _addressGenerator.NextVirtualAddress();
            }

            State.CurrentStep = TranslationStep.SplitVA;
        }

        private void ExecuteSplitVa()
        {
            State.CurrentAddressParts = AddressTranslatorUtils.SplitVirtualAddress(
                State.CurrentVirtualAddress,
                _config.vaBits,
                _derivedConfig.OffsetBits);

            State.CurrentStep = TranslationStep.LookupTLB;
        }

        private void ExecuteLookupTlb()
        {
            if (_tlbCache.TryLookup(State.CurrentAddressParts.Vpn, out var pfn))
            {
                State.IsTlbHit = true;
                State.CurrentPfn = pfn;
                State.HasCurrentPfn = true;
                State.CurrentStep = TranslationStep.ComposePA;
                return;
            }

            State.CurrentStep = TranslationStep.LookupPageTable;
        }

        private void ExecuteLookupPageTable()
        {
            if (_pageTable.TryGetPresentPfn(
                State.CurrentAddressParts.L1Index,
                State.CurrentAddressParts.L2Index,
                out var pfn))
            {
                State.IsPageTableHit = true;
                State.CurrentPfn = pfn;
                State.HasCurrentPfn = true;
                _tlbCache.InsertOrUpdate(State.CurrentAddressParts.Vpn, pfn);
                State.CurrentStep = TranslationStep.ComposePA;
                return;
            }

            State.CurrentStep = TranslationStep.HandlePageFault;
        }

        private void ExecuteHandlePageFault()
        {
            var result = MemoryAccessResolver.Resolve(
                State.CurrentAddressParts.Vpn,
                State.CurrentAddressParts.L1Index,
                State.CurrentAddressParts.L2Index,
                _pageTable,
                _physicalMemoryManager,
                _tlbCache);

            State.IsPageTableHit = result.PageTableHit;
            State.IsPageFault = result.PageFault;
            State.CurrentPfn = result.Pfn;
            State.HasCurrentPfn = true;
            _lastEviction = result.Eviction;
            State.CurrentStep = TranslationStep.ComposePA;
        }

        private void ExecuteComposePa()
        {
            if (!State.HasCurrentPfn)
            {
                throw new InvalidOperationException("ComposePA 阶段缺少 PFN，无法合成物理地址。");
            }

            State.CurrentPhysicalAddress =
                (State.CurrentPfn << _derivedConfig.OffsetBits) | State.CurrentAddressParts.Offset;

            State.CurrentStep = TranslationStep.Finalize;
        }

        private void ExecuteFinalize()
        {
            State.CurrentRound += 1;
            State.CurrentStep = TranslationStep.GenerateVA;
        }

        private void EnsureVirtualAddressInRange(ulong virtualAddress)
        {
            if (_config.vaBits < 64 && virtualAddress > _addressGenerator.MaxVirtualAddress)
            {
                throw new ArgumentOutOfRangeException(nameof(virtualAddress), "virtualAddress 超出 vaBits 可表示范围。");
            }
        }
    }
}
