using Ryujinx.Graphics.Device;
using Ryujinx.Graphics.Gpu.Engine.Compute;
using Ryujinx.Graphics.Gpu.Engine.Dma;
using Ryujinx.Graphics.Gpu.Engine.InlineToMemory;
using Ryujinx.Graphics.Gpu.Engine.Threed;
using Ryujinx.Graphics.Gpu.Engine.Twod;
using Ryujinx.Graphics.Gpu.Memory;
using System;
using System.Runtime.CompilerServices;
namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
{
    /// 
    /// Represents a GPU General Purpose FIFO command processor.
    /// 
    class GPFifoProcessor
    {
        private const int MacrosCount = 0x80;
        private const int MacroIndexMask = MacrosCount - 1;
        private const int LoadInlineDataMethodOffset = 0x6d;
        private const int UniformBufferUpdateDataMethodOffset = 0x8e4;
        private readonly GpuChannel _channel;
        /// 
        /// Channel memory manager.
        /// 
        public MemoryManager MemoryManager => _channel.MemoryManager;
        /// 
        /// 3D Engine.
        /// 
        public ThreedClass ThreedClass => _3dClass;
        /// 
        /// Internal GPFIFO state.
        /// 
        private struct DmaState
        {
            public int Method;
            public int SubChannel;
            public int MethodCount;
            public bool NonIncrementing;
            public bool IncrementOnce;
        }
        private DmaState _state;
        private readonly ThreedClass _3dClass;
        private readonly ComputeClass _computeClass;
        private readonly InlineToMemoryClass _i2mClass;
        private readonly TwodClass _2dClass;
        private readonly DmaClass _dmaClass;
        private readonly GPFifoClass _fifoClass;
        /// 
        /// Creates a new instance of the GPU General Purpose FIFO command processor.
        /// 
        /// GPU context
        /// Channel that the GPFIFO processor belongs to
        public GPFifoProcessor(GpuContext context, GpuChannel channel)
        {
            _channel = channel;
            _fifoClass = new GPFifoClass(context, this);
            _3dClass = new ThreedClass(context, channel, _fifoClass);
            _computeClass = new ComputeClass(context, channel, _3dClass);
            _i2mClass = new InlineToMemoryClass(context, channel);
            _2dClass = new TwodClass(channel);
            _dmaClass = new DmaClass(context, channel, _3dClass);
        }
        /// 
        /// Processes a command buffer.
        /// 
        /// Base GPU virtual address of the command buffer
        /// Command buffer
        public void Process(ulong baseGpuVa, ReadOnlySpan commandBuffer)
        {
            for (int index = 0; index < commandBuffer.Length; index++)
            {
                int command = commandBuffer[index];
                ulong gpuVa = baseGpuVa + (ulong)index * 4;
                if (_state.MethodCount != 0)
                {
                    if (TryFastI2mBufferUpdate(commandBuffer, ref index))
                    {
                        continue;
                    }
                    Send(gpuVa, _state.Method, command, _state.SubChannel, _state.MethodCount <= 1);
                    if (!_state.NonIncrementing)
                    {
                        _state.Method++;
                    }
                    if (_state.IncrementOnce)
                    {
                        _state.NonIncrementing = true;
                    }
                    _state.MethodCount--;
                }
                else
                {
                    CompressedMethod meth = Unsafe.As(ref command);
                    if (TryFastUniformBufferUpdate(meth, commandBuffer, index))
                    {
                        index += meth.MethodCount;
                        continue;
                    }
                    switch (meth.SecOp)
                    {
                        case SecOp.IncMethod:
                        case SecOp.NonIncMethod:
                        case SecOp.OneInc:
                            _state.Method = meth.MethodAddress;
                            _state.SubChannel = meth.MethodSubchannel;
                            _state.MethodCount = meth.MethodCount;
                            _state.IncrementOnce = meth.SecOp == SecOp.OneInc;
                            _state.NonIncrementing = meth.SecOp == SecOp.NonIncMethod;
                            break;
                        case SecOp.ImmdDataMethod:
                            Send(gpuVa, meth.MethodAddress, meth.ImmdData, meth.MethodSubchannel, true);
                            break;
                    }
                }
            }
            _3dClass.FlushUboDirty();
        }
        /// 
        /// Tries to perform a fast Inline-to-Memory data update.
        /// If successful, all data will be copied at once, and 
        /// command buffer entries will be consumed.
        /// 
        /// Command buffer where the data is contained
        /// Offset at  where the data is located, auto-incremented on success
        /// True if the fast copy was successful, false otherwise
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private bool TryFastI2mBufferUpdate(ReadOnlySpan commandBuffer, ref int offset)
        {
            if (_state.Method == LoadInlineDataMethodOffset && _state.NonIncrementing && _state.SubChannel <= 2)
            {
                int availableCount = commandBuffer.Length - offset;
                int consumeCount = Math.Min(_state.MethodCount, availableCount);
                var data = commandBuffer.Slice(offset, consumeCount);
                if (_state.SubChannel == 0)
                {
                    _3dClass.LoadInlineData(data);
                }
                else if (_state.SubChannel == 1)
                {
                    _computeClass.LoadInlineData(data);
                }
                else /* if (_state.SubChannel == 2) */
                {
                    _i2mClass.LoadInlineData(data);
                }
                offset += consumeCount - 1;
                _state.MethodCount -= consumeCount;
                return true;
            }
            return false;
        }
        /// 
        /// Tries to perform a fast constant buffer data update.
        /// If successful, all data will be copied at once, and  + 1
        /// command buffer entries will be consumed.
        /// 
        /// Compressed method to be checked
        /// Command buffer where  is contained
        /// Offset at  where  is located
        /// True if the fast copy was successful, false otherwise
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private bool TryFastUniformBufferUpdate(CompressedMethod meth, ReadOnlySpan commandBuffer, int offset)
        {
            int availableCount = commandBuffer.Length - offset;
            if (meth.MethodAddress == UniformBufferUpdateDataMethodOffset &&
                meth.MethodCount < availableCount &&
                meth.SecOp == SecOp.NonIncMethod)
            {
                _3dClass.ConstantBufferUpdate(commandBuffer.Slice(offset + 1, meth.MethodCount));
                return true;
            }
            return false;
        }
        /// 
        /// Sends a uncompressed method for processing by the graphics pipeline.
        /// 
        /// GPU virtual address where the command word is located
        /// Method to be processed
        private void Send(ulong gpuVa, int offset, int argument, int subChannel, bool isLastCall)
        {
            if (offset < 0x60)
            {
                _fifoClass.Write(offset * 4, argument);
            }
            else if (offset < 0xe00)
            {
                offset *= 4;
                switch (subChannel)
                {
                    case 0:
                        _3dClass.Write(offset, argument);
                        break;
                    case 1:
                        _computeClass.Write(offset, argument);
                        break;
                    case 2:
                        _i2mClass.Write(offset, argument);
                        break;
                    case 3:
                        _2dClass.Write(offset, argument);
                        break;
                    case 4:
                        _dmaClass.Write(offset, argument);
                        break;
                }
            }
            else
            {
                IDeviceState state = subChannel switch
                {
                    0 => _3dClass,
                    3 => _2dClass,
                    _ => null
                };
                if (state != null)
                {
                    int macroIndex = (offset >> 1) & MacroIndexMask;
                    if ((offset & 1) != 0)
                    {
                        _fifoClass.MmePushArgument(macroIndex, gpuVa, argument);
                    }
                    else
                    {
                        _fifoClass.MmeStart(macroIndex, argument);
                    }
                    if (isLastCall)
                    {
                        _fifoClass.CallMme(macroIndex, state);
                        _3dClass.PerformDeferredDraws();
                    }
                }
            }
        }
        /// 
        /// Writes data directly to the state of the specified class.
        /// 
        /// ID of the class to write the data into
        /// State offset in bytes
        /// Value to be written
        public void Write(ClassId classId, int offset, int value)
        {
            switch (classId)
            {
                case ClassId.Threed:
                    _3dClass.Write(offset, value);
                    break;
                case ClassId.Compute:
                    _computeClass.Write(offset, value);
                    break;
                case ClassId.InlineToMemory:
                    _i2mClass.Write(offset, value);
                    break;
                case ClassId.Twod:
                    _2dClass.Write(offset, value);
                    break;
                case ClassId.Dma:
                    _dmaClass.Write(offset, value);
                    break;
                case ClassId.GPFifo:
                    _fifoClass.Write(offset, value);
                    break;
            }
        }
        /// 
        /// Sets the shadow ram control value of all sub-channels.
        /// 
        /// New shadow ram control value
        public void SetShadowRamControl(int control)
        {
            _3dClass.SetShadowRamControl(control);
        }
        /// 
        /// Forces a full host state update by marking all state as modified,
        /// and also requests all GPU resources in use to be rebound.
        /// 
        public void ForceAllDirty()
        {
            _3dClass.ForceStateDirty();
            _channel.BufferManager.Rebind();
            _channel.TextureManager.Rebind();
        }
        /// 
        /// Perform any deferred draws.
        /// 
        public void PerformDeferredDraws()
        {
            _3dClass.PerformDeferredDraws();
        }
    }
}