using ChocolArm64.Memory;
using System;

namespace Ryujinx.Graphics.Memory
{
    class NvGpuVmmCache
    {
        private ValueRangeSet<int> CachedRanges;

        public NvGpuVmmCache()
        {
            CachedRanges = new ValueRangeSet<int>();
        }

        public bool IsRegionModified(MemoryManager Memory, NvGpuBufferType BufferType, long PA, long Size)
        {
            (bool[] Modified, long ModifiedCount) = Memory.IsRegionModified(PA, Size);

            //Remove all modified ranges.
            int Index = 0;

            long Position = PA & ~NvGpuVmm.PageMask;

            while (ModifiedCount > 0)
            {
                if (Modified[Index++])
                {
                    CachedRanges.Remove(new ValueRange<int>(Position, Position + NvGpuVmm.PageSize));

                    ModifiedCount--;
                }

                Position += NvGpuVmm.PageSize;
            }

            //Mask has the bit set for the current resource type.
            //If the region is not yet present on the list, then a new ValueRange
            //is directly added with the current resource type as the only bit set.
            //Otherwise, it just sets the bit for this new resource type on the current mask.
            int Mask = 1 << (int)BufferType;

            ValueRange<int> NewCached = new ValueRange<int>(PA, PA + Size);

            ValueRange<int>[] Ranges = CachedRanges.GetAllIntersections(NewCached);

            long LastEnd = NewCached.Start;

            long Coverage = 0;

            for (Index = 0; Index < Ranges.Length; Index++)
            {
                ValueRange<int> Current = Ranges[Index];

                long RgStart = Math.Max(Current.Start, NewCached.Start);
                long RgEnd   = Math.Min(Current.End,   NewCached.End);

                if ((Current.Value & Mask) == 0)
                {
                    CachedRanges.Add(new ValueRange<int>(RgStart, RgEnd, Current.Value | Mask));
                }
                else
                {
                    Coverage += RgEnd - RgStart;
                }

                if (RgStart > LastEnd)
                {
                    CachedRanges.Add(new ValueRange<int>(LastEnd, RgStart, Mask));
                }

                LastEnd = RgEnd;
            }

            if (LastEnd < NewCached.End)
            {
                CachedRanges.Add(new ValueRange<int>(LastEnd, NewCached.End, Mask));
            }

            return Coverage != Size;
        }
    }
}