using Ryujinx.Common.Memory;
using System;
using System.Numerics;

namespace Ryujinx.Graphics.Vulkan
{
    interface IBitMapListener
    {
        void BitMapSignal(int index, int count);
    }

    struct BitMapStruct<T> where T : IArray<long>
    {
        public const int IntSize = 64;

        private const int IntShift = 6;
        private const int IntMask = IntSize - 1;

        private T _masks;

        public BitMapStruct()
        {
            _masks = default;
        }

        public bool BecomesUnsetFrom(in BitMapStruct<T> from, ref BitMapStruct<T> into)
        {
            bool result = false;
            
            Span<long> masksSpan = _masks.AsSpan();
            Span<long> fMasksSpan = from._masks.AsSpan();
            Span<long> iMasksSpan = into._masks.AsSpan();

            int masks = masksSpan.Length;
            for (int i = 0; i < masks; i++)
            {
                long fromMask = fMasksSpan[i];
                long unsetMask = (~fromMask) & (fromMask ^ masksSpan[i]);
                iMasksSpan[i] = unsetMask;

                result |= unsetMask != 0;
            }

            return result;
        }

        public void SetAndSignalUnset<T2>(in BitMapStruct<T> from, ref T2 listener) where T2 : struct, IBitMapListener
        {
            BitMapStruct<T> result = new();

            if (BecomesUnsetFrom(from, ref result))
            {
                // Iterate the set bits in the result, and signal them.

                int offset = 0;
                Span<long> rMasksSpan = result._masks.AsSpan();
                int masks = rMasksSpan.Length;
                for (int i = 0; i < masks; i++)
                {
                    long value = rMasksSpan[i];
                    while (value != 0)
                    {
                        int bit = BitOperations.TrailingZeroCount((ulong)value);

                        listener.BitMapSignal(offset + bit, 1);

                        value &= ~(1L << bit);
                    }

                    offset += IntSize;
                }
            }

            _masks = from._masks;
        }

        public void SignalSet(Action<int, int> action)
        {
            // Iterate the set bits in the result, and signal them.

            int offset = 0;
            Span<long> masksSpan = _masks.AsSpan();
            int masks = masksSpan.Length;
            for (int i = 0; i < masks; i++)
            {
                long value = masksSpan[i];
                while (value != 0)
                {
                    int bit = BitOperations.TrailingZeroCount((ulong)value);

                    action(offset + bit, 1);

                    value &= ~(1L << bit);
                }

                offset += IntSize;
            }
        }

        public bool AnySet()
        {
            Span<long> masksSpan = _masks.AsSpan();
            
            for (int i = 0; i < masksSpan.Length; i++)
            {
                if (masksSpan[i] != 0)
                {
                    return true;
                }
            }

            return false;
        }

        public bool IsSet(int bit)
        {
            int wordIndex = bit >> IntShift;
            int wordBit = bit & IntMask;

            long wordMask = 1L << wordBit;

            return (_masks[wordIndex] & wordMask) != 0;
        }

        public bool IsSet(int start, int end)
        {
            if (start == end)
            {
                return IsSet(start);
            }

            int startIndex = start >> IntShift;
            int startBit = start & IntMask;
            long startMask = -1L << startBit;

            int endIndex = end >> IntShift;
            int endBit = end & IntMask;
            long endMask = (long)(ulong.MaxValue >> (IntMask - endBit));

            if (startIndex == endIndex)
            {
                return (_masks[startIndex] & startMask & endMask) != 0;
            }

            if ((_masks[startIndex] & startMask) != 0)
            {
                return true;
            }
            
            Span<long> masksSpan = _masks.AsSpan();

            for (int i = startIndex + 1; i < endIndex; i++)
            {
                if (masksSpan[i] != 0)
                {
                    return true;
                }
            }

            if ((_masks[endIndex] & endMask) != 0)
            {
                return true;
            }

            return false;
        }

        public bool Set(int bit)
        {
            int wordIndex = bit >> IntShift;
            int wordBit = bit & IntMask;

            long wordMask = 1L << wordBit;

            if ((_masks[wordIndex] & wordMask) != 0)
            {
                return false;
            }

            _masks[wordIndex] |= wordMask;

            return true;
        }

        public void Set(int bit, bool value)
        {
            if (value)
            {
                Set(bit);
            }
            else
            {
                Clear(bit);
            }
        }

        public void SetRange(int start, int end)
        {
            if (start == end)
            {
                Set(start);
                return;
            }

            int startIndex = start >> IntShift;
            int startBit = start & IntMask;
            long startMask = -1L << startBit;

            int endIndex = end >> IntShift;
            int endBit = end & IntMask;
            long endMask = (long)(ulong.MaxValue >> (IntMask - endBit));
            
            Span<long> masksSpan = _masks.AsSpan();

            if (startIndex == endIndex)
            {
                masksSpan[startIndex] |= startMask & endMask;
            }
            else
            {
                masksSpan[startIndex] |= startMask;

                for (int i = startIndex + 1; i < endIndex; i++)
                {
                    masksSpan[i] |= -1L;
                }

                masksSpan[endIndex] |= endMask;
            }
        }

        public BitMapStruct<T> Union(BitMapStruct<T> other)
        {
            var result = new BitMapStruct<T>();

            var masksSpan = _masks.AsSpan();
            var oMasksSpan = other._masks.AsSpan();
            var nMasksSpan = result._masks.AsSpan();

            for (int i = 0; i < masksSpan.Length; i++)
            {
                nMasksSpan[i] = masksSpan[i] | oMasksSpan[i];
            }

            return result;
        }

        public void Clear(int bit)
        {
            int wordIndex = bit >> IntShift;
            int wordBit = bit & IntMask;

            long wordMask = 1L << wordBit;

            _masks[wordIndex] &= ~wordMask;
        }

        public void Clear()
        {
            Span<long> masksSpan = _masks.AsSpan();
            
            for (int i = 0; i < masksSpan.Length; i++)
            {
                masksSpan[i] = 0;
            }
        }

        public void ClearInt(int start, int end)
        {
            Span<long> masksSpan = _masks.AsSpan();
            
            for (int i = start; i <= end; i++)
            {
                masksSpan[i] = 0;
            }
        }
    }
}
