using NUnit.Framework;
using System;
using System.IO;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine.PathTracing.Integration;
using UnityEngine.PathTracing.Lightmapping;
using UnityEngine.Rendering;
using UnityEngine.Rendering.UnifiedRayTracing;
using UnityEditor;

namespace UnityEngine.PathTracing.Tests
{
    [TestFixture("Compute")]
    [TestFixture("Hardware")]
    internal class ResourceCacheTests
    {
        RayTracingBackend _backend;
        RayTracingContext _context;
        CommandBuffer m_Cmd;
        UVFallbackBufferBuilder m_UVFBBuilder;
        LightmapIntegrationResourceCache m_ResourceCache;
        ChartRasterizer m_ChartRasterizer;
        ChartRasterizer.Buffers m_ChartRasterizerBuffers;

        public ResourceCacheTests(string backendAsString)
        {
            _backend = Enum.Parse<RayTracingBackend>(backendAsString);
        }

        void CreateRayTracingResources()
        {
            var resources = new RayTracingResources();
            resources.Load();
            _context = new RayTracingContext(_backend, resources);
            m_UVFBBuilder = new UVFallbackBufferBuilder();
            Material uvFbMaterial = new(Shader.Find("Hidden/UVFallbackBufferGeneration"));

            m_UVFBBuilder.Prepare(uvFbMaterial);
            m_ResourceCache = new LightmapIntegrationResourceCache();

            ChartRasterizer.LoadShaders(out var software, out var hardware);
            m_ChartRasterizer = new ChartRasterizer(software, hardware);

            int maxIndexCount = 6; // 2 triangles
            m_ChartRasterizerBuffers = new ChartRasterizer.Buffers()
            {
                vertex = new GraphicsBuffer(GraphicsBuffer.Target.Structured, maxIndexCount, UnsafeUtility.SizeOf<Vector2>()),
                vertexToOriginalVertex = new GraphicsBuffer(GraphicsBuffer.Target.Structured, maxIndexCount, sizeof(uint)),
                vertexToChartID = new GraphicsBuffer(GraphicsBuffer.Target.Structured, maxIndexCount, sizeof(uint)),
            };
        }

        void DisposeRayTracingResources()
        {
            m_ResourceCache?.Dispose();
            m_UVFBBuilder?.Dispose();
            m_ChartRasterizer?.Dispose();
            m_ChartRasterizerBuffers.vertex?.Dispose();
            m_ChartRasterizerBuffers.vertexToOriginalVertex?.Dispose();
            m_ChartRasterizerBuffers.vertexToChartID?.Dispose();
            _context?.Dispose();
        }

        [SetUp]
        public void SetUp()
        {
            if (!SystemInfo.supportsRayTracing && _backend == RayTracingBackend.Hardware)
            {
                Assert.Ignore("Cannot run test on this Graphics API. Hardware RayTracing is not supported");
            }

            if (!SystemInfo.supportsComputeShaders && _backend == RayTracingBackend.Compute)
            {
                Assert.Ignore("Cannot run test on this Graphics API. Compute shaders are not supported");
            }

            if (SystemInfo.graphicsDeviceName.Contains("llvmpipe"))
            {
                Assert.Ignore("Cannot run test on this device (Renderer: llvmpipe (LLVM 10.0.0, 128 bits)). Tests are disabled because they fail on some platforms (that do not support 11 SSBOs). Once we do not run Ubuntu 18.04 try removing this");
            }

            CreateRayTracingResources();
            m_Cmd = new();
        }

        [TearDown]
        public void TearDown()
        {
            DisposeRayTracingResources();
            m_Cmd?.Dispose();
        }

        [Test]
        public void ResourceCache_AddInstance_ResourcesAreAdded()
        {
            Mesh mesh = TestUtils.CreateSingleTriangleMesh();
            BakeInstance instance = new();
            instance.Build(mesh, new Vector4(1, 1, 0, 0), new Vector4(1, 1, 0, 0), new Vector2Int(10, 10), Vector2Int.zero, Matrix4x4.identity, true, LodIdentifier.Invalid, 0);
            BakeInstance[] instances = { instance };
            Assert.IsTrue(m_ResourceCache.AddResources(instances, _context, m_Cmd, m_UVFBBuilder), "Expected that the instance could be added to cache.");
            Assert.AreEqual(1, m_ResourceCache.UVMeshCount(), "Expected that the cache has one uv mesh.");
            Assert.AreEqual(1, m_ResourceCache.UVAccelerationStructureCount(), "Expected that the cache has one uv acceleration structure.");
            Assert.AreEqual(1, m_ResourceCache.UVFallbackBufferCount(), "Expected that the cache has one uv fallback buffer.");
        }

        [Test]
        public void ResourceCache_AddTwoInstancesSameMesh_OnlyOneSetOfResourcesAreAdded()
        {
            Mesh mesh = TestUtils.CreateSingleTriangleMesh();
            BakeInstance instance1 = new();
            instance1.Build(mesh, new Vector4(1, 1, 0, 0), new Vector4(1, 1, 0, 0), new Vector2Int(10, 10), Vector2Int.zero, Matrix4x4.identity, true, LodIdentifier.Invalid, 0);
            BakeInstance instance2 = new();
            instance2.Build(mesh, new Vector4(1, 1, 0, 0), new Vector4(1, 1, 0, 0), new Vector2Int(10, 10), Vector2Int.zero, Matrix4x4.identity, true, LodIdentifier.Invalid, 0);
            BakeInstance[] instances = { instance1, instance2 };
            Assert.IsTrue(m_ResourceCache.AddResources(instances, _context, m_Cmd, m_UVFBBuilder), "Expected that the instance could be added to cache.");
            Assert.AreEqual(1, m_ResourceCache.UVMeshCount(), "Expected that the cache has one uv mesh.");
            Assert.AreEqual(1, m_ResourceCache.UVAccelerationStructureCount(), "Expected that the cache has one uv acceleration structure.");
            Assert.AreEqual(1, m_ResourceCache.UVFallbackBufferCount(), "Expected that the cache has one uv fallback buffer.");
        }

        [Test]
        public void ResourceCache_AddTwoInstancesDifferentMesh_TwoSetsOfResourcesAreAdded()
        {
            Mesh mesh1 = TestUtils.CreateSingleTriangleMesh();
            Mesh mesh2 = TestUtils.CreateQuadMesh();
            BakeInstance instance1 = new();
            instance1.Build(mesh1, new Vector4(1, 1, 0, 0), new Vector4(1, 1, 0, 0), new Vector2Int(10, 10), Vector2Int.zero, Matrix4x4.identity, true, LodIdentifier.Invalid, 0);
            BakeInstance instance2 = new();
            instance2.Build(mesh2, new Vector4(1, 1, 0, 0), new Vector4(1, 1, 0, 0), new Vector2Int(10, 10), Vector2Int.zero, Matrix4x4.identity, true, LodIdentifier.Invalid, 0);
            BakeInstance[] instances = { instance1, instance2 };
            Assert.IsTrue(m_ResourceCache.AddResources(instances, _context, m_Cmd, m_UVFBBuilder), "Expected that the instance could be added to cache.");
            Assert.AreEqual(2, m_ResourceCache.UVMeshCount(), "Expected that the cache has 2 uv meshes.");
            Assert.AreEqual(2, m_ResourceCache.UVAccelerationStructureCount(), "Expected that the cache has two uv acceleration structures.");
            Assert.AreEqual(2, m_ResourceCache.UVFallbackBufferCount(), "Expected that the cache has two uv fallback buffers.");
        }

        [Test]
        public void ResourceCache_AddTwoInstanceSameMeshDifferentResolution_TwoFallbackBuffersAreAdded()
        {
            Mesh mesh1 = TestUtils.CreateSingleTriangleMesh();
            Mesh mesh2 = mesh1;
            BakeInstance instance1 = new();
            instance1.Build(mesh1, new Vector4(1, 1, 0, 0), new Vector4(1, 1, 0, 0), new Vector2Int(5, 5), Vector2Int.zero, Matrix4x4.identity, true, LodIdentifier.Invalid, 0);
            BakeInstance instance2 = new();
            instance2.Build(mesh2, new Vector4(1, 1, 0, 0), new Vector4(1, 1, 0, 0), new Vector2Int(10, 10), Vector2Int.zero, Matrix4x4.identity, true, LodIdentifier.Invalid, 0);
            BakeInstance[] instances = { instance1, instance2 };
            Assert.IsTrue(m_ResourceCache.AddResources(instances, _context, m_Cmd, m_UVFBBuilder), "Expected that the instance could be added to cache.");
            Assert.AreEqual(1, m_ResourceCache.UVMeshCount(), "Expected that the cache has one uv mesh.");
            Assert.AreEqual(1, m_ResourceCache.UVAccelerationStructureCount(), "Expected that the cache has one uv acceleration structure.");
            Assert.AreEqual(2, m_ResourceCache.UVFallbackBufferCount(), "Expected that the cache has two uv fallback buffers due to the different resolution.");
        }
    }
}