using Ryujinx.Graphics.Device;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Gpu.Engine.Threed
{
    /// 
    /// State update callback entry, with the callback function and associated field names.
    /// 
    readonly struct StateUpdateCallbackEntry
    {
        /// 
        /// Callback function, to be called if the register was written as the state needs to be updated.
        /// 
        public Action Callback { get; }
        /// 
        /// Name of the state fields (registers) associated with the callback function.
        /// 
        public string[] FieldNames { get; }
        /// 
        /// Creates a new state update callback entry.
        /// 
        /// Callback function, to be called if the register was written as the state needs to be updated
        /// Name of the state fields (registers) associated with the callback function
        public StateUpdateCallbackEntry(Action callback, params string[] fieldNames)
        {
            Callback = callback;
            FieldNames = fieldNames;
        }
    }
    /// 
    /// GPU state update tracker.
    /// 
    /// State type
    class StateUpdateTracker<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] TState>
    {
        private const int BlockSize = 0xe00;
        private const int RegisterSize = sizeof(uint);
        private readonly byte[] _registerToGroupMapping;
        private readonly Action[] _callbacks;
        private ulong _dirtyMask;
        /// 
        /// Creates a new instance of the state update tracker.
        /// 
        /// Update tracker callback entries
        public StateUpdateTracker(StateUpdateCallbackEntry[] entries)
        {
            _registerToGroupMapping = new byte[BlockSize];
            _callbacks = new Action[entries.Length];
            var fieldToDelegate = new Dictionary();
            for (int entryIndex = 0; entryIndex < entries.Length; entryIndex++)
            {
                var entry = entries[entryIndex];
                foreach (var fieldName in entry.FieldNames)
                {
                    fieldToDelegate.Add(fieldName, entryIndex);
                }
                _callbacks[entryIndex] = entry.Callback;
            }
            var fields = typeof(TState).GetFields();
            int offset = 0;
            for (int fieldIndex = 0; fieldIndex < fields.Length; fieldIndex++)
            {
                var field = fields[fieldIndex];
                int sizeOfField = SizeCalculator.SizeOf(field.FieldType);
                if (fieldToDelegate.TryGetValue(field.Name, out int entryIndex))
                {
                    for (int i = 0; i < ((sizeOfField + 3) & ~3); i += 4)
                    {
                        _registerToGroupMapping[(offset + i) / RegisterSize] = (byte)(entryIndex + 1);
                    }
                }
                offset += sizeOfField;
            }
            Debug.Assert(offset == Unsafe.SizeOf());
        }
        /// 
        /// Sets a register as modified.
        /// 
        /// Register offset in bytes
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void SetDirty(int offset)
        {
            uint index = (uint)offset / RegisterSize;
            if (index < BlockSize)
            {
                int groupIndex = Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(_registerToGroupMapping), (IntPtr)index);
                if (groupIndex != 0)
                {
                    groupIndex--;
                    _dirtyMask |= 1UL << groupIndex;
                }
            }
        }
        /// 
        /// Forces a register group as dirty, by index.
        /// 
        /// Index of the group to be dirtied
        public void ForceDirty(int groupIndex)
        {
            if ((uint)groupIndex >= _callbacks.Length)
            {
                throw new ArgumentOutOfRangeException(nameof(groupIndex));
            }
            _dirtyMask |= 1UL << groupIndex;
        }
        /// 
        /// Forces all register groups as dirty, triggering a full update on the next call to .
        /// 
        public void SetAllDirty()
        {
            Debug.Assert(_callbacks.Length <= sizeof(ulong) * 8);
            _dirtyMask = ulong.MaxValue >> ((sizeof(ulong) * 8) - _callbacks.Length);
        }
        /// 
        /// Check if the given register group is dirty without clearing it.
        /// 
        /// Index of the group to check
        /// True if dirty, false otherwise
        public bool IsDirty(int groupIndex)
        {
            return (_dirtyMask & (1UL << groupIndex)) != 0;
        }
        /// 
        /// Check all the groups specified by  for modification, and update if modified.
        /// 
        /// Mask, where each bit set corresponds to a group index that should be checked
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void Update(ulong checkMask)
        {
            ulong mask = _dirtyMask & checkMask;
            if (mask == 0)
            {
                return;
            }
            do
            {
                int groupIndex = BitOperations.TrailingZeroCount(mask);
                _callbacks[groupIndex]();
                mask &= ~(1UL << groupIndex);
            }
            while (mask != 0);
            _dirtyMask &= ~checkMask;
        }
    }
}