using Ryujinx.Common;
using Ryujinx.Graphics.Gpu.State;
using Ryujinx.Graphics.Texture;
using System;
using System.Runtime.InteropServices;

namespace Ryujinx.Graphics.Gpu.Engine
{
    partial class Methods
    {
        private Inline2MemoryParams _params;

        private bool _isLinear;

        private int _offset;
        private int _size;

        private bool _finished;

        private int[] _buffer;

        /// <summary>
        /// Launches Inline-to-Memory engine DMA copy.
        /// </summary>
        /// <param name="state">Current GPU state</param>
        /// <param name="argument">Method call argument</param>
        public void LaunchDma(GpuState state, int argument)
        {
            _params = state.Get<Inline2MemoryParams>(MethodOffset.I2mParams);

            _isLinear = (argument & 1) != 0;

            _offset = 0;
            _size   = _params.LineLengthIn * _params.LineCount;

            int count = BitUtils.DivRoundUp(_size, 4);

            if (_buffer == null || _buffer.Length < count)
            {
                _buffer = new int[count];
            }

            ulong dstBaseAddress = _context.MemoryManager.Translate(_params.DstAddress.Pack());

            _context.Methods.TextureManager.Flush(dstBaseAddress, (ulong)_size);

            _finished = false;
        }

        /// <summary>
        /// Pushes a word of data to the Inline-to-Memory engine.
        /// </summary>
        /// <param name="state">Current GPU state</param>
        /// <param name="argument">Method call argument</param>
        public void LoadInlineData(GpuState state, int argument)
        {
            if (!_finished)
            {
                _buffer[_offset++] = argument;

                if (_offset * 4 >= _size)
                {
                    FinishTransfer();
                }
            }
        }

        /// <summary>
        /// Performs actual copy of the inline data after the transfer is finished.
        /// </summary>
        private void FinishTransfer()
        {
            Span<byte> data = MemoryMarshal.Cast<int, byte>(_buffer).Slice(0, _size);

            if (_isLinear && _params.LineCount == 1)
            {
                ulong address = _context.MemoryManager.Translate( _params.DstAddress.Pack());

                _context.PhysicalMemory.Write(address, data);
            }
            else
            {
                var dstCalculator = new OffsetCalculator(
                    _params.DstWidth,
                    _params.DstHeight,
                    _params.DstStride,
                    _isLinear,
                    _params.DstMemoryLayout.UnpackGobBlocksInY(),
                    1);

                int srcOffset = 0;

                ulong dstBaseAddress = _context.MemoryManager.Translate(_params.DstAddress.Pack());

                for (int y = _params.DstY; y < _params.DstY + _params.LineCount; y++)
                {
                    int x1      = _params.DstX;
                    int x2      = _params.DstX + _params.LineLengthIn;
                    int x2Trunc = _params.DstX + BitUtils.AlignDown(_params.LineLengthIn, 16);

                    int x;

                    for (x = x1; x < x2Trunc; x += 16, srcOffset += 16)
                    {
                        int dstOffset = dstCalculator.GetOffset(x, y);

                        ulong dstAddress = dstBaseAddress + (ulong)dstOffset;

                        Span<byte> pixel = data.Slice(srcOffset, 16);

                        _context.PhysicalMemory.Write(dstAddress, pixel);
                    }

                    for (; x < x2; x++, srcOffset++)
                    {
                        int dstOffset = dstCalculator.GetOffset(x, y);

                        ulong dstAddress = dstBaseAddress + (ulong)dstOffset;

                        Span<byte> pixel = data.Slice(srcOffset, 1);

                        _context.PhysicalMemory.Write(dstAddress, pixel);
                    }
                }
            }

            _finished = true;
        }
    }
}