using Ryujinx.Common;
using Ryujinx.Common.Logging;
using System;
using System.IO;

namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
{
    /// <summary>
    /// Represents a background disk cache writer.
    /// </summary>
    class BackgroundDiskCacheWriter : IDisposable
    {
        /// <summary>
        /// Possible operation to do on the <see cref="_fileWriterWorkerQueue"/>.
        /// </summary>
        private enum CacheFileOperation
        {
            /// <summary>
            /// Operation to add a shader to the cache.
            /// </summary>
            AddShader
        }

        /// <summary>
        /// Represents an operation to perform on the <see cref="_fileWriterWorkerQueue"/>.
        /// </summary>
        private readonly struct CacheFileOperationTask
        {
            /// <summary>
            /// The type of operation to perform.
            /// </summary>
            public readonly CacheFileOperation Type;

            /// <summary>
            /// The data associated to this operation or null.
            /// </summary>
            public readonly object Data;

            public CacheFileOperationTask(CacheFileOperation type, object data)
            {
                Type = type;
                Data = data;
            }
        }

        /// <summary>
        /// Background shader cache write information.
        /// </summary>
        private readonly struct AddShaderData
        {
            /// <summary>
            /// Cached shader program.
            /// </summary>
            public readonly CachedShaderProgram Program;

            /// <summary>
            /// Binary host code.
            /// </summary>
            public readonly byte[] HostCode;

            /// <summary>
            /// Creates a new background shader cache write information.
            /// </summary>
            /// <param name="program">Cached shader program</param>
            /// <param name="hostCode">Binary host code</param>
            public AddShaderData(CachedShaderProgram program, byte[] hostCode)
            {
                Program = program;
                HostCode = hostCode;
            }
        }

        private readonly GpuContext _context;
        private readonly DiskCacheHostStorage _hostStorage;
        private readonly AsyncWorkQueue<CacheFileOperationTask> _fileWriterWorkerQueue;

        /// <summary>
        /// Creates a new background disk cache writer.
        /// </summary>
        /// <param name="context">GPU context</param>
        /// <param name="hostStorage">Disk cache host storage</param>
        public BackgroundDiskCacheWriter(GpuContext context, DiskCacheHostStorage hostStorage)
        {
            _context = context;
            _hostStorage = hostStorage;
            _fileWriterWorkerQueue = new AsyncWorkQueue<CacheFileOperationTask>(ProcessTask, "GPU.BackgroundDiskCacheWriter");
        }

        /// <summary>
        /// Processes a shader cache background operation.
        /// </summary>
        /// <param name="task">Task to process</param>
        private void ProcessTask(CacheFileOperationTask task)
        {
            switch (task.Type)
            {
                case CacheFileOperation.AddShader:
                    AddShaderData data = (AddShaderData)task.Data;
                    try
                    {
                        _hostStorage.AddShader(_context, data.Program, data.HostCode);
                    }
                    catch (DiskCacheLoadException diskCacheLoadException)
                    {
                        Logger.Error?.Print(LogClass.Gpu, $"Error writing shader to disk cache. {diskCacheLoadException.Message}");
                    }
                    catch (IOException ioException)
                    {
                        Logger.Error?.Print(LogClass.Gpu, $"Error writing shader to disk cache. {ioException.Message}");
                    }
                    break;
            }
        }

        /// <summary>
        /// Adds a shader program to be cached in the background.
        /// </summary>
        /// <param name="program">Shader program to cache</param>
        /// <param name="hostCode">Host binary code of the program</param>
        public void AddShader(CachedShaderProgram program, byte[] hostCode)
        {
            _fileWriterWorkerQueue.Add(new CacheFileOperationTask(CacheFileOperation.AddShader, new AddShaderData(program, hostCode)));
        }

        public void Dispose()
        {
            Dispose(true);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                _fileWriterWorkerQueue.Dispose();
            }
        }
    }
}