using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Image;
using Ryujinx.Graphics.Texture;
using Ryujinx.Memory.Range;
using System;
using System.Collections.Concurrent;
using System.Threading;
namespace Ryujinx.Graphics.Gpu
{
    using Texture = Image.Texture;
    /// 
    /// GPU image presentation window.
    ///  
    public class Window
    {
        private readonly GpuContext _context;
        /// 
        /// Texture presented on the window.
        ///  
        private readonly struct PresentationTexture
        {
            /// 
            /// Texture cache where the texture might be located.
            ///  
            public TextureCache Cache { get; }
            /// 
            /// Texture information.
            ///  
            public TextureInfo Info { get; }
            /// 
            /// Physical memory locations where the texture data is located.
            ///  
            public MultiRange Range { get; }
            /// 
            /// Texture crop region.
            ///  
            public ImageCrop Crop { get; }
            /// 
            /// Texture acquire callback.
            ///  
            public Action AcquireCallback { get; }
            /// 
            /// Texture release callback.
            ///  
            public Action ReleaseCallback { get; }
            /// 
            /// User defined object, passed to the various callbacks.
            ///  
            public object UserObj { get; }
            /// 
            /// Creates a new instance of the presentation texture.
            ///  
            ///  acquireCallback,
                Action             releaseCallback,
                object                     userObj)
            {
                Cache           = cache;
                Info            = info;
                Range           = range;
                Crop            = crop;
                AcquireCallback = acquireCallback;
                ReleaseCallback = releaseCallback;
                UserObj         = userObj;
            }
        }
        private readonly ConcurrentQueue _frameQueue;
        private int _framesAvailable;
        public bool IsFrameAvailable => _framesAvailable != 0;
        /// 
        /// Creates a new instance of the GPU presentation window.
        ///  
        /// ();
        }
        /// 
        /// Enqueues a frame for presentation.
        /// This method is thread safe and can be called from any thread.
        /// When the texture is presented and not needed anymore, the release callback is called.
        /// It's an error to modify the texture after calling this method, before the release callback is called.
        ///  
        /// Thrown when  
        /// True if the frame was added to the queue, false otherwise 
        public bool EnqueueFrameThreadSafe(
            ulong                      pid,
            ulong                      address,
            int                        width,
            int                        height,
            int                        stride,
            bool                       isLinear,
            int                        gobBlocksInY,
            Format                     format,
            int                        bytesPerPixel,
            ImageCrop                  crop,
            Action acquireCallback,
            Action             releaseCallback,
            object                     userObj)
        {
            if (!_context.PhysicalMemoryRegistry.TryGetValue(pid, out var physicalMemory))
            {
                return false;
            }
            FormatInfo formatInfo = new FormatInfo(format, 1, 1, bytesPerPixel, 4);
            TextureInfo info = new TextureInfo(
                0UL,
                width,
                height,
                1,
                1,
                1,
                1,
                stride,
                isLinear,
                gobBlocksInY,
                1,
                1,
                Target.Texture2D,
                formatInfo);
            int size = SizeCalculator.GetBlockLinearTextureSize(
                width,
                height,
                1,
                1,
                1,
                1,
                1,
                bytesPerPixel,
                gobBlocksInY,
                1,
                1).TotalSize;
            MultiRange range = new MultiRange(address, (ulong)size);
            _frameQueue.Enqueue(new PresentationTexture(
                physicalMemory.TextureCache,
                info,
                range,
                crop,
                acquireCallback,
                releaseCallback,
                userObj));
            return true;
        }
        /// 
        /// Presents a texture on the queue.
        /// If the queue is empty, then no texture is presented.
        ///  
        /// 
        /// Indicate that a frame on the queue is ready to be acquired.
        ///  
        public void SignalFrameReady()
        {
            Interlocked.Increment(ref _framesAvailable);
        }
        /// 
        /// Determine if any frames are available, and decrement the available count if there are.
        ///  
        /// True if a frame is available, false otherwise 
        public bool ConsumeFrameAvailable()
        {
            if (Interlocked.CompareExchange(ref _framesAvailable, 0, 0) != 0)
            {
                Interlocked.Decrement(ref _framesAvailable);
                return true;
            }
            return false;
        }
    }
}