using System;
using System.Collections.Generic;

namespace Ryujinx.Graphics.Gpu.Image
{
    /// <summary>
    /// Resource pool interface.
    /// </summary>
    /// <typeparam name="T">Resource pool type</typeparam>
    interface IPool<T>
    {
        /// <summary>
        /// Start address of the pool in memory.
        /// </summary>
        ulong Address { get; }

        /// <summary>
        /// Linked list node used on the texture pool cache.
        /// </summary>
        LinkedListNode<T> CacheNode { get; set; }

        /// <summary>
        /// Timestamp set on the last use of the pool by the cache.
        /// </summary>
        ulong CacheTimestamp { get; set; }
    }

    /// <summary>
    /// Pool cache.
    /// This can keep multiple pools, and return the current one as needed.
    /// </summary>
    abstract class PoolCache<T> : IDisposable where T : IPool<T>, IDisposable
    {
        private const int MaxCapacity = 2;
        private const ulong MinDeltaForRemoval = 20000;

        private readonly GpuContext _context;
        private readonly LinkedList<T> _pools;
        private ulong _currentTimestamp;

        /// <summary>
        /// Constructs a new instance of the pool.
        /// </summary>
        /// <param name="context">GPU context that the texture pool belongs to</param>
        public PoolCache(GpuContext context)
        {
            _context = context;
            _pools = new LinkedList<T>();
        }

        /// <summary>
        /// Increments the internal timestamp of the cache that is used to decide when old resources will be deleted.
        /// </summary>
        public void Tick()
        {
            _currentTimestamp++;
        }

        /// <summary>
        /// Finds a cache texture pool, or creates a new one if not found.
        /// </summary>
        /// <param name="channel">GPU channel that the texture pool cache belongs to</param>
        /// <param name="address">Start address of the texture pool</param>
        /// <param name="maximumId">Maximum ID of the texture pool</param>
        /// <returns>The found or newly created texture pool</returns>
        public T FindOrCreate(GpuChannel channel, ulong address, int maximumId)
        {
            // Remove old entries from the cache, if possible.
            while (_pools.Count > MaxCapacity && (_currentTimestamp - _pools.First.Value.CacheTimestamp) >= MinDeltaForRemoval)
            {
                T oldestPool = _pools.First.Value;

                _pools.RemoveFirst();
                oldestPool.Dispose();
                oldestPool.CacheNode = null;
            }

            T pool;

            // Try to find the pool on the cache.
            for (LinkedListNode<T> node = _pools.First; node != null; node = node.Next)
            {
                pool = node.Value;

                if (pool.Address == address)
                {
                    if (pool.CacheNode != _pools.Last)
                    {
                        _pools.Remove(pool.CacheNode);

                        pool.CacheNode = _pools.AddLast(pool);
                    }

                    pool.CacheTimestamp = _currentTimestamp;

                    return pool;
                }
            }

            // If not found, create a new one.
            pool = CreatePool(_context, channel, address, maximumId);

            pool.CacheNode = _pools.AddLast(pool);
            pool.CacheTimestamp = _currentTimestamp;

            return pool;
        }

        /// <summary>
        /// Creates a new instance of the pool.
        /// </summary>
        /// <param name="context">GPU context that the pool belongs to</param>
        /// <param name="channel">GPU channel that the pool belongs to</param>
        /// <param name="address">Address of the pool in guest memory</param>
        /// <param name="maximumId">Maximum ID of the pool (equal to maximum minus one)</param>
        protected abstract T CreatePool(GpuContext context, GpuChannel channel, ulong address, int maximumId);

        public void Dispose()
        {
            foreach (T pool in _pools)
            {
                pool.Dispose();
                pool.CacheNode = null;
            }

            _pools.Clear();
        }
    }
}
