using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.GAL.Texture;
using Ryujinx.Graphics.Gpu.Memory;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;

namespace Ryujinx.Graphics.Gpu.Image
{
    class TexturePool : Pool<Texture>
    {
        private TextureManager _textureManager;

        public LinkedListNode<TexturePool> CacheNode { get; set; }

        private struct TextureContainer
        {
            public Texture Texture0 { get; set; }
            public Texture Texture1 { get; set; }
        }

        public TexturePool(
            GpuContext     context,
            TextureManager textureManager,
            ulong          address,
            int            maximumId) : base(context, address, maximumId)
        {
            _textureManager = textureManager;
        }

        public override Texture Get(int id)
        {
            if ((uint)id >= Items.Length)
            {
                return null;
            }

            SynchronizeMemory();

            Texture texture = Items[id];

            if (texture == null)
            {
                ulong address = Address + (ulong)(uint)id * DescriptorSize;

                Span<byte> data = Context.PhysicalMemory.Read(address, DescriptorSize);

                TextureDescriptor descriptor = MemoryMarshal.Cast<byte, TextureDescriptor>(data)[0];

                TextureInfo info = GetInfo(descriptor);

                // Bad address. We can't add a texture with a invalid address
                // to the cache.
                if (info.Address == MemoryManager.BadAddress)
                {
                    return null;
                }

                texture = _textureManager.FindOrCreateTexture(info, TextureSearchFlags.Sampler);

                texture.IncrementReferenceCount();

                Items[id] = texture;
            }
            else
            {
                // Memory is automatically synchronized on texture creation.
                texture.SynchronizeMemory();
            }

            return texture;
        }

        protected override void InvalidateRangeImpl(ulong address, ulong size)
        {
            ulong endAddress = address + size;

            for (; address < endAddress; address += DescriptorSize)
            {
                int id = (int)((address - Address) / DescriptorSize);

                Texture texture = Items[id];

                if (texture != null)
                {
                    Span<byte> data = Context.PhysicalMemory.Read(address, DescriptorSize);

                    TextureDescriptor descriptor = MemoryMarshal.Cast<byte, TextureDescriptor>(data)[0];

                    // If the descriptors are the same, the texture is the same,
                    // we don't need to remove as it was not modified. Just continue.
                    if (texture.IsPerfectMatch(GetInfo(descriptor), TextureSearchFlags.Strict))
                    {
                        continue;
                    }

                    texture.DecrementReferenceCount();

                    Items[id] = null;
                }
            }
        }

        private TextureInfo GetInfo(TextureDescriptor descriptor)
        {
            ulong address = Context.MemoryManager.Translate(descriptor.UnpackAddress());

            int width         = descriptor.UnpackWidth();
            int height        = descriptor.UnpackHeight();
            int depthOrLayers = descriptor.UnpackDepth();
            int levels        = descriptor.UnpackLevels();

            TextureMsaaMode msaaMode = descriptor.UnpackTextureMsaaMode();

            int samplesInX = msaaMode.SamplesInX();
            int samplesInY = msaaMode.SamplesInY();

            int stride = descriptor.UnpackStride();

            TextureDescriptorType descriptorType = descriptor.UnpackTextureDescriptorType();

            bool isLinear = descriptorType == TextureDescriptorType.Linear;

            Target target = descriptor.UnpackTextureTarget().Convert((samplesInX | samplesInY) != 1);

            uint format = descriptor.UnpackFormat();
            bool srgb   = descriptor.UnpackSrgb();

            if (!FormatTable.TryGetTextureFormat(format, srgb, out FormatInfo formatInfo))
            {
                // TODO: Warning.

                formatInfo = FormatInfo.Default;
            }

            int gobBlocksInY = descriptor.UnpackGobBlocksInY();
            int gobBlocksInZ = descriptor.UnpackGobBlocksInZ();

            int gobBlocksInTileX = descriptor.UnpackGobBlocksInTileX();

            SwizzleComponent swizzleR = descriptor.UnpackSwizzleR().Convert();
            SwizzleComponent swizzleG = descriptor.UnpackSwizzleG().Convert();
            SwizzleComponent swizzleB = descriptor.UnpackSwizzleB().Convert();
            SwizzleComponent swizzleA = descriptor.UnpackSwizzleA().Convert();

            DepthStencilMode depthStencilMode = GetDepthStencilMode(
                formatInfo.Format,
                swizzleR,
                swizzleG,
                swizzleB,
                swizzleA);

            return new TextureInfo(
                address,
                width,
                height,
                depthOrLayers,
                levels,
                samplesInX,
                samplesInY,
                stride,
                isLinear,
                gobBlocksInY,
                gobBlocksInZ,
                gobBlocksInTileX,
                target,
                formatInfo,
                depthStencilMode,
                swizzleR,
                swizzleG,
                swizzleB,
                swizzleA);
        }

        private static DepthStencilMode GetDepthStencilMode(Format format, params SwizzleComponent[] components)
        {
            // R = Depth, G = Stencil.
            // On 24-bits depth formats, this is inverted (Stencil is R etc).
            // NVN setup:
            // For depth, A is set to 1.0f, the other components are set to Depth.
            // For stencil, all components are set to Stencil.
            SwizzleComponent component = components[0];

            for (int index = 1; index < 4 && !IsRG(component); index++)
            {
                component = components[index];
            }

            if (!IsRG(component))
            {
                return DepthStencilMode.Depth;
            }

            if (format == Format.D24X8Unorm || format == Format.D24UnormS8Uint)
            {
                return component == SwizzleComponent.Red
                    ? DepthStencilMode.Stencil
                    : DepthStencilMode.Depth;
            }
            else
            {
                return component == SwizzleComponent.Red
                    ? DepthStencilMode.Depth
                    : DepthStencilMode.Stencil;
            }
        }

        private static bool IsRG(SwizzleComponent component)
        {
            return component == SwizzleComponent.Red ||
                   component == SwizzleComponent.Green;
        }

        protected override void Delete(Texture item)
        {
            item?.DecrementReferenceCount();
        }
    }
}