using Ryujinx.Graphics.Gpu.State;
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 readonly GpuContext _context;
        /// 
        /// 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 GpuState[] _subChannels;
        private readonly GPFifoClass _fifoClass;
        /// 
        /// Creates a new instance of the GPU General Purpose FIFO command processor.
        /// 
        /// GPU context
        public GPFifoProcessor(GpuContext context)
        {
            _context = context;
            _fifoClass = new GPFifoClass(context, this);
            _subChannels = new GpuState[8];
            for (int index = 0; index < _subChannels.Length; index++)
            {
                _subChannels[index] = new GpuState();
                _context.Methods.RegisterCallbacks(_subChannels[index]);
            }
        }
        /// 
        /// Processes a command buffer.
        /// 
        /// Command buffer
        public void Process(ReadOnlySpan commandBuffer)
        {
            for (int index = 0; index < commandBuffer.Length; index++)
            {
                int command = commandBuffer[index];
                if (_state.MethodCount != 0)
                {
                    Send(new MethodParams(_state.Method, command, _state.SubChannel, _state.MethodCount));
                    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(new MethodParams(meth.MethodAddress, meth.ImmdData, meth.MethodSubchannel, 1));
                            break;
                    }
                }
            }
        }
        /// 
        /// 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
        private bool TryFastUniformBufferUpdate(CompressedMethod meth, ReadOnlySpan commandBuffer, int offset)
        {
            int availableCount = commandBuffer.Length - offset;
            if (meth.MethodCount < availableCount &&
                meth.SecOp == SecOp.NonIncMethod &&
                meth.MethodAddress == (int)MethodOffset.UniformBufferUpdateData)
            {
                GpuState state = _subChannels[meth.MethodSubchannel];
                _context.Methods.UniformBufferUpdate(state, commandBuffer.Slice(offset + 1, meth.MethodCount));
                return true;
            }
            return false;
        }
        /// 
        /// Sends a uncompressed method for processing by the graphics pipeline.
        /// 
        /// Method to be processed
        private void Send(MethodParams meth)
        {
            if ((MethodOffset)meth.Method == MethodOffset.BindChannel)
            {
                _subChannels[meth.SubChannel] = new GpuState();
                _context.Methods.RegisterCallbacks(_subChannels[meth.SubChannel]);
            }
            else if (meth.Method < 0x60)
            {
                // TODO: check if macros are shared between subchannels or not. For now let's assume they are.
                _fifoClass.Write(meth.Method * 4, meth.Argument);
            }
            else if (meth.Method < 0xe00)
            {
                _subChannels[meth.SubChannel].CallMethod(meth);
            }
            else
            {
                int macroIndex = (meth.Method >> 1) & MacroIndexMask;
                if ((meth.Method & 1) != 0)
                {
                    _fifoClass.MmePushArgument(macroIndex, meth.Argument);
                }
                else
                {
                    _fifoClass.MmeStart(macroIndex, meth.Argument);
                }
                if (meth.IsLastCall)
                {
                    _fifoClass.CallMme(macroIndex, _subChannels[meth.SubChannel]);
                    _context.Methods.PerformDeferredDraws();
                }
            }
        }
        /// 
        /// Sets the shadow ram control value of all sub-channels.
        /// 
        /// New shadow ram control value
        public void SetShadowRamControl(ShadowRamControl control)
        {
            for (int i = 0; i < _subChannels.Length; i++)
            {
                _subChannels[i].ShadowRamControl = control;
            }
        }
    }
}