using Ryujinx.Common;
using Ryujinx.Graphics.GAL;
using System;
using System.Runtime.InteropServices;

namespace Ryujinx.Graphics.Gpu.Engine.MME
{
    /// <summary>
    /// Table with information about High-level implementations of GPU Macro code.
    /// </summary>
    static class MacroHLETable
    {
        /// <summary>
        /// Macroo High-level implementation table entry.
        /// </summary>
        readonly struct TableEntry
        {
            /// <summary>
            /// Name of the Macro function.
            /// </summary>
            public MacroHLEFunctionName Name { get; }

            /// <summary>
            /// Hash of the original binary Macro function code.
            /// </summary>
            public Hash128 Hash { get; }

            /// <summary>
            /// Size (in bytes) of the original binary Macro function code.
            /// </summary>
            public int Length { get; }

            /// <summary>
            /// Creates a new table entry.
            /// </summary>
            /// <param name="name">Name of the Macro function</param>
            /// <param name="hash">Hash of the original binary Macro function code</param>
            /// <param name="length">Size (in bytes) of the original binary Macro function code</param>
            public TableEntry(MacroHLEFunctionName name, Hash128 hash, int length)
            {
                Name = name;
                Hash = hash;
                Length = length;
            }
        }

        private static readonly TableEntry[] _table = new TableEntry[]
        {
            new TableEntry(MacroHLEFunctionName.ClearColor, new Hash128(0xA9FB28D1DC43645A, 0xB177E5D2EAE67FB0), 0x28),
            new TableEntry(MacroHLEFunctionName.ClearDepthStencil, new Hash128(0x1B96CB77D4879F4F, 0x8557032FE0C965FB), 0x24),
            new TableEntry(MacroHLEFunctionName.DrawArraysInstanced, new Hash128(0x197FB416269DBC26, 0x34288C01DDA82202), 0x48),
            new TableEntry(MacroHLEFunctionName.DrawElementsInstanced, new Hash128(0x1A501FD3D54EC8E0, 0x6CF570CF79DA74D6), 0x5c),
            new TableEntry(MacroHLEFunctionName.DrawElementsIndirect, new Hash128(0x86A3E8E903AF8F45, 0xD35BBA07C23860A4), 0x7c),
            new TableEntry(MacroHLEFunctionName.MultiDrawElementsIndirectCount, new Hash128(0x890AF57ED3FB1C37, 0x35D0C95C61F5386F), 0x19C)
        };

        /// <summary>
        /// Checks if the host supports all features required by the HLE macro.
        /// </summary>
        /// <param name="caps">Host capabilities</param>
        /// <param name="name">Name of the HLE macro to be checked</param>
        /// <returns>True if the host supports the HLE macro, false otherwise</returns>
        private static bool IsMacroHLESupported(Capabilities caps, MacroHLEFunctionName name)
        {
            if (name == MacroHLEFunctionName.ClearColor ||
                name == MacroHLEFunctionName.ClearDepthStencil ||
                name == MacroHLEFunctionName.DrawArraysInstanced ||
                name == MacroHLEFunctionName.DrawElementsInstanced ||
                name == MacroHLEFunctionName.DrawElementsIndirect)
            {
                return true;
            }
            else if (name == MacroHLEFunctionName.MultiDrawElementsIndirectCount)
            {
                return caps.SupportsIndirectParameters;
            }

            return false;
        }

        /// <summary>
        /// Checks if there's a fast, High-level implementation of the specified Macro code available.
        /// </summary>
        /// <param name="code">Macro code to be checked</param>
        /// <param name="caps">Renderer capabilities to check for this macro HLE support</param>
        /// <param name="name">Name of the function if a implementation is available and supported, otherwise <see cref="MacroHLEFunctionName.None"/></param>
        /// <returns>True if there is a implementation available and supported, false otherwise</returns>
        public static bool TryGetMacroHLEFunction(ReadOnlySpan<int> code, Capabilities caps, out MacroHLEFunctionName name)
        {
            var mc = MemoryMarshal.Cast<int, byte>(code);

            for (int i = 0; i < _table.Length; i++)
            {
                ref var entry = ref _table[i];

                var hash = XXHash128.ComputeHash(mc.Slice(0, entry.Length));
                if (hash == entry.Hash)
                {
                    if (IsMacroHLESupported(caps, entry.Name))
                    {
                        name = entry.Name;
                        return true;
                    }

                    break;
                }
            }

            name = MacroHLEFunctionName.None;
            return false;
        }
    }
}
