using Ryujinx.Common;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Engine.GPFifo;
using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Gpu.Shader;
using Ryujinx.Graphics.Gpu.Synchronization;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
namespace Ryujinx.Graphics.Gpu
{
    /// 
    /// GPU emulation context.
    /// 
    public sealed class GpuContext : IDisposable
    {
        private const int NsToTicksFractionNumerator = 384;
        private const int NsToTicksFractionDenominator = 625;
        /// 
        /// Event signaled when the host emulation context is ready to be used by the gpu context.
        /// 
        public ManualResetEvent HostInitalized { get; }
        /// 
        /// Host renderer.
        /// 
        public IRenderer Renderer { get; }
        /// 
        /// GPU General Purpose FIFO queue.
        /// 
        public GPFifoDevice GPFifo { get; }
        /// 
        /// GPU synchronization manager.
        /// 
        public SynchronizationManager Synchronization { get; }
        /// 
        /// Presentation window.
        /// 
        public Window Window { get; }
        /// 
        /// Internal sequence number, used to avoid needless resource data updates
        /// in the middle of a command buffer before synchronizations.
        /// 
        internal int SequenceNumber { get; private set; }
        /// 
        /// Internal sync number, used to denote points at which host synchronization can be requested.
        /// 
        internal ulong SyncNumber { get; private set; }
        /// 
        /// Actions to be performed when a CPU waiting syncpoint or barrier is triggered.
        /// If there are more than 0 items when this happens, a host sync object will be generated for the given ,
        /// and the SyncNumber will be incremented.
        /// 
        internal List SyncActions { get; }
        /// 
        /// Actions to be performed when a CPU waiting syncpoint is triggered.
        /// If there are more than 0 items when this happens, a host sync object will be generated for the given ,
        /// and the SyncNumber will be incremented.
        /// 
        internal List SyncpointActions { get; }
        /// 
        /// Queue with deferred actions that must run on the render thread.
        /// 
        internal Queue DeferredActions { get; }
        /// 
        /// Registry with physical memories that can be used with this GPU context, keyed by owner process ID.
        /// 
        internal ConcurrentDictionary PhysicalMemoryRegistry { get; }
        /// 
        /// Host hardware capabilities.
        /// 
        internal Capabilities Capabilities { get; private set; }
        /// 
        /// Event for signalling shader cache loading progress.
        /// 
        public event Action ShaderCacheStateChanged;
        private Thread _gpuThread;
        /// 
        /// Creates a new instance of the GPU emulation context.
        /// 
        /// Host renderer
        public GpuContext(IRenderer renderer)
        {
            Renderer = renderer;
            GPFifo = new GPFifoDevice(this);
            Synchronization = new SynchronizationManager();
            Window = new Window(this);
            HostInitalized = new ManualResetEvent(false);
            SyncActions = new List();
            SyncpointActions = new List();
            DeferredActions = new Queue();
            PhysicalMemoryRegistry = new ConcurrentDictionary();
        }
        /// 
        /// Creates a new GPU channel.
        /// 
        /// The GPU channel
        public GpuChannel CreateChannel()
        {
            return new GpuChannel(this);
        }
        /// 
        /// Creates a new GPU memory manager.
        /// 
        /// ID of the process that owns the memory manager
        /// The memory manager
        /// Thrown when  is invalid
        public MemoryManager CreateMemoryManager(ulong pid)
        {
            if (!PhysicalMemoryRegistry.TryGetValue(pid, out var physicalMemory))
            {
                throw new ArgumentException("The PID is invalid or the process was not registered", nameof(pid));
            }
            return new MemoryManager(physicalMemory);
        }
        /// 
        /// Registers virtual memory used by a process for GPU memory access, caching and read/write tracking.
        /// 
        /// ID of the process that owns 
        /// Virtual memory owned by the process
        /// Thrown if  was already registered
        public void RegisterProcess(ulong pid, Cpu.IVirtualMemoryManagerTracked cpuMemory)
        {
            var physicalMemory = new PhysicalMemory(this, cpuMemory);
            if (!PhysicalMemoryRegistry.TryAdd(pid, physicalMemory))
            {
                throw new ArgumentException("The PID was already registered", nameof(pid));
            }
            physicalMemory.ShaderCache.ShaderCacheStateChanged += ShaderCacheStateUpdate;
        }
        /// 
        /// Unregisters a process, indicating that its memory will no longer be used, and that caches can be freed.
        /// 
        /// ID of the process
        public void UnregisterProcess(ulong pid)
        {
            if (PhysicalMemoryRegistry.TryRemove(pid, out var physicalMemory))
            {
                physicalMemory.ShaderCache.ShaderCacheStateChanged -= ShaderCacheStateUpdate;
                physicalMemory.Dispose();
            }
        }
        /// 
        /// Converts a nanoseconds timestamp value to Maxwell time ticks.
        /// 
        /// 
        /// The frequency is 614400000 Hz.
        /// 
        /// Timestamp in nanoseconds
        /// Maxwell ticks
        private static ulong ConvertNanosecondsToTicks(ulong nanoseconds)
        {
            // We need to divide first to avoid overflows.
            // We fix up the result later by calculating the difference and adding
            // that to the result.
            ulong divided = nanoseconds / NsToTicksFractionDenominator;
            ulong rounded = divided * NsToTicksFractionDenominator;
            ulong errorBias = (nanoseconds - rounded) * NsToTicksFractionNumerator / NsToTicksFractionDenominator;
            return divided * NsToTicksFractionNumerator + errorBias;
        }
        /// 
        /// Gets the value of the GPU timer.
        /// 
        /// The current GPU timestamp
        public ulong GetTimestamp()
        {
            ulong ticks = ConvertNanosecondsToTicks((ulong)PerformanceCounter.ElapsedNanoseconds);
            if (GraphicsConfig.FastGpuTime)
            {
                // Divide by some amount to report time as if operations were performed faster than they really are.
                // This can prevent some games from switching to a lower resolution because rendering is too slow.
                ticks /= 256;
            }
            return ticks;
        }
        /// 
        /// Shader cache state update handler.
        /// 
        /// Current state of the shader cache load process
        /// Number of the current shader being processed
        /// Total number of shaders to process
        private void ShaderCacheStateUpdate(ShaderCacheState state, int current, int total)
        {
            ShaderCacheStateChanged?.Invoke(state, current, total);
        }
        /// 
        /// Initialize the GPU shader cache.
        /// 
        public void InitializeShaderCache(CancellationToken cancellationToken)
        {
            HostInitalized.WaitOne();
            foreach (var physicalMemory in PhysicalMemoryRegistry.Values)
            {
                physicalMemory.ShaderCache.Initialize(cancellationToken);
            }
        }
        /// 
        /// Sets the current thread as the main GPU thread.
        /// 
        public void SetGpuThread()
        {
            _gpuThread = Thread.CurrentThread;
            Capabilities = Renderer.GetCapabilities();
        }
        /// 
        /// Checks if the current thread is the GPU thread.
        /// 
        /// True if the thread is the GPU thread, false otherwise
        public bool IsGpuThread()
        {
            return _gpuThread == Thread.CurrentThread;
        }
        /// 
        /// Processes the queue of shaders that must save their binaries to the disk cache.
        /// 
        public void ProcessShaderCacheQueue()
        {
            foreach (var physicalMemory in PhysicalMemoryRegistry.Values)
            {
                physicalMemory.ShaderCache.ProcessShaderCacheQueue();
            }
        }
        /// 
        /// Advances internal sequence number.
        /// This forces the update of any modified GPU resource.
        /// 
        internal void AdvanceSequence()
        {
            SequenceNumber++;
        }
        /// 
        /// Registers an action to be performed the next time a syncpoint is incremented.
        /// This will also ensure a host sync object is created, and  is incremented.
        /// 
        /// The action to be performed on sync object creation
        /// True if the sync action should only run when syncpoints are incremented
        public void RegisterSyncAction(Action action, bool syncpointOnly = false)
        {
            if (syncpointOnly)
            {
                SyncpointActions.Add(action);
            }
            else
            {
                SyncActions.Add(action);
            }
        }
        /// 
        /// Creates a host sync object if there are any pending sync actions. The actions will then be called.
        /// If no actions are present, a host sync object is not created.
        /// 
        /// True if host sync is being created by a syncpoint
        public void CreateHostSyncIfNeeded(bool syncpoint)
        {
            if (SyncActions.Count > 0 || (syncpoint && SyncpointActions.Count > 0))
            {
                Renderer.CreateSync(SyncNumber);
                SyncNumber++;
                foreach (Action action in SyncActions)
                {
                    action();
                }
                foreach (Action action in SyncpointActions)
                {
                    action();
                }
                SyncActions.Clear();
                SyncpointActions.Clear();
            }
        }
        /// 
        /// Performs deferred actions.
        /// This is useful for actions that must run on the render thread, such as resource disposal.
        /// 
        internal void RunDeferredActions()
        {
            while (DeferredActions.TryDequeue(out Action action))
            {
                action();
            }
        }
        /// 
        /// Disposes all GPU resources currently cached.
        /// It's an error to push any GPU commands after disposal.
        /// Additionally, the GPU commands FIFO must be empty for disposal,
        /// and processing of all commands must have finished.
        /// 
        public void Dispose()
        {
            Renderer.Dispose();
            GPFifo.Dispose();
            HostInitalized.Dispose();
            // Has to be disposed before processing deferred actions, as it will produce some.
            foreach (var physicalMemory in PhysicalMemoryRegistry.Values)
            {
                physicalMemory.Dispose();
            }
            PhysicalMemoryRegistry.Clear();
            RunDeferredActions();
        }
    }
}