using System;
using UnityEngine.Experimental.Rendering;
using UnityEngine.Rendering.RenderGraphModule;

namespace UnityEngine.Rendering.Universal
{
    /// <summary>
    /// Generate HDR debug data into the given color target
    /// </summary>
    internal class HDRDebugViewPass : ScriptableRenderPass
    {
        private enum HDRDebugPassId
        {
            CIExyPrepass = 0,
            DebugViewPass = 1
        }

        RTHandle m_PassthroughRT;
        Material m_material;

        /// <summary>
        /// Creates a new <c>HDRDebugViewPass</c> instance.
        /// </summary>
        /// <param name="mat">The <c>Material</c> to use.</param>
        /// <seealso cref="RenderPassEvent"/>
        public HDRDebugViewPass(Material mat)
        {
            profilingSampler = new ProfilingSampler("Blit HDR Debug Data");
            renderPassEvent = RenderPassEvent.AfterRendering + 3;
            m_material = mat;
        }

        // Common to RenderGraph and non-RenderGraph paths
        private class PassDataCIExy
        {
            internal Material material;
            internal Vector4 luminanceParameters;
            internal TextureHandle srcColor;
            internal TextureHandle xyBuffer;
            internal TextureHandle passThrough;
        }

        private class PassDataDebugView
        {
            internal Material material;
            internal HDRDebugMode hdrDebugMode;
            internal UniversalCameraData cameraData;
            internal Vector4 luminanceParameters;
            internal TextureHandle xyBuffer;
            internal TextureHandle srcColor;
            internal TextureHandle dstColor;
        }

        public static void ConfigureDescriptorForCIEPrepass(ref RenderTextureDescriptor descriptor)
        {
            descriptor.graphicsFormat = GraphicsFormat.R32_SFloat;
            descriptor.width = descriptor.height = ShaderConstants._SizeOfHDRXYMapping;
            descriptor.useMipMap = false;
            descriptor.autoGenerateMips = false;
            descriptor.useDynamicScale = true;
            descriptor.depthStencilFormat = GraphicsFormat.None;
            descriptor.enableRandomWrite = true;
            descriptor.msaaSamples = 1;
            descriptor.dimension = TextureDimension.Tex2D;
            descriptor.vrUsage = VRTextureUsage.None; // We only need one for both eyes in VR
        }

        internal static Vector4 GetLuminanceParameters(UniversalCameraData cameraData)
        {
            var luminanceParams = Vector4.zero;
            if (cameraData.isHDROutputActive)
            {
                Tonemapping tonemapping = VolumeManager.instance.stack.GetComponent<Tonemapping>();
                UniversalRenderPipeline.GetHDROutputLuminanceParameters(cameraData.hdrDisplayInformation, cameraData.hdrDisplayColorGamut, tonemapping, out luminanceParams);
            }
            else
            {
                luminanceParams.z = 1.0f;
            }
            return luminanceParams;
        }

        private static void ExecuteCIExyPrepass(CommandBuffer cmd, PassDataCIExy data, RTHandle sourceTexture, RTHandle xyTarget, RTHandle destTexture)
        {            
            CoreUtils.SetRenderTarget(cmd, destTexture, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.DontCare, ClearFlag.None, Color.clear);

            Vector4 debugParameters = new Vector4(ShaderConstants._SizeOfHDRXYMapping, ShaderConstants._SizeOfHDRXYMapping, 0, 0);

            cmd.SetRandomWriteTarget(ShaderConstants._CIExyUAVIndex, xyTarget);
            data.material.SetVector(ShaderConstants._HDRDebugParamsId, debugParameters);
            data.material.SetVector(ShaderPropertyId.hdrOutputLuminanceParams, data.luminanceParameters);

            Vector2 viewportScale = sourceTexture.useScaling ? new Vector2(sourceTexture.rtHandleProperties.rtHandleScale.x, sourceTexture.rtHandleProperties.rtHandleScale.y) : Vector2.one;
            Blitter.BlitTexture(cmd, sourceTexture, viewportScale, data.material, 0);

            cmd.ClearRandomWriteTargets();
        }

        private static void ExecuteHDRDebugViewFinalPass(RasterCommandBuffer cmd, in PassDataDebugView data, RTHandle source, Vector4 scaleBias, RTHandle destination, RTHandle xyTarget)
        {
            if (data.cameraData.isHDROutputActive)
            {
                HDROutputUtils.ConfigureHDROutput(data.material, data.cameraData.hdrDisplayColorGamut, HDROutputUtils.Operation.ColorEncoding);
                CoreUtils.SetKeyword(data.material, ShaderKeywordStrings.HDROverlay, data.cameraData.rendersOverlayUI);
            }

            data.material.SetTexture(ShaderConstants._xyTextureId, xyTarget);

            Vector4 debugParameters = new Vector4(ShaderConstants._SizeOfHDRXYMapping, ShaderConstants._SizeOfHDRXYMapping, 0, 0);
            data.material.SetVector(ShaderConstants._HDRDebugParamsId, debugParameters);
            data.material.SetVector(ShaderPropertyId.hdrOutputLuminanceParams, data.luminanceParameters);
            data.material.SetInteger(ShaderConstants._DebugHDRModeId, (int)data.hdrDebugMode);            

            RenderTargetIdentifier cameraTarget = BuiltinRenderTextureType.CameraTarget;
            #if ENABLE_VR && ENABLE_XR_MODULE
                if (data.cameraData.xr.enabled)
                    cameraTarget = data.cameraData.xr.renderTarget;
            #endif

            if (destination.nameID == cameraTarget || data.cameraData.targetTexture != null)
                cmd.SetViewport(data.cameraData.pixelRect);

            Blitter.BlitTexture(cmd, source, scaleBias, data.material, 1);            
        }

        // Non-RenderGraph path
        public void Dispose()
        {
            m_PassthroughRT?.Release();
        }

        /// <summary>
        /// Configure the pass
        /// </summary>
        /// <param name="cameraData">Descriptor for the color buffer.</param>
        /// <param name="hdrdebugMode">Active DebugMode for HDR.</param>
        public void Setup(UniversalCameraData cameraData, HDRDebugMode hdrdebugMode)
        {
            RenderTextureDescriptor descriptor = cameraData.cameraTargetDescriptor;
            DebugHandler.ConfigureColorDescriptorForDebugScreen(ref descriptor, cameraData.pixelWidth, cameraData.pixelHeight);
            RenderingUtils.ReAllocateHandleIfNeeded(ref m_PassthroughRT, descriptor, name: "_HDRDebugDummyRT");
        }
        
        internal void RenderHDRDebug(RenderGraph renderGraph, UniversalCameraData cameraData, TextureHandle srcColor, TextureHandle overlayUITexture, TextureHandle dstColor, HDRDebugMode hdrDebugMode)
        {
            bool requiresCIExyData = hdrDebugMode != HDRDebugMode.ValuesAbovePaperWhite;
            Vector4 luminanceParameters = GetLuminanceParameters(cameraData);

            TextureHandle intermediateRT = srcColor;
            TextureHandle xyBuffer = TextureHandle.nullHandle;

            if (requiresCIExyData)
            {
                RenderTextureDescriptor descriptor = cameraData.cameraTargetDescriptor;
                DebugHandler.ConfigureColorDescriptorForDebugScreen(ref descriptor, cameraData.pixelWidth, cameraData.pixelHeight);
                intermediateRT = UniversalRenderer.CreateRenderGraphTexture(renderGraph, descriptor, "_HDRDebugDummyRT", false);

                ConfigureDescriptorForCIEPrepass(ref descriptor);
                xyBuffer = UniversalRenderer.CreateRenderGraphTexture(renderGraph, descriptor, "_xyBuffer", true);

                // Using low level pass because of random UAV support, and since this is a debug view, we don't care much about merging passes or optimizing for TBDR.
                // This could be a compute pass (like in HDRP) but doing it in pixel is compatible with devices that might support HDR output but not compute shaders.
                using (var builder = renderGraph.AddUnsafePass<PassDataCIExy>("Blit HDR DebugView CIExy", out var passData, profilingSampler))
                {
                    passData.material = m_material;
                    passData.luminanceParameters = luminanceParameters;
                    passData.srcColor = srcColor;
                    builder.UseTexture(srcColor);
                    passData.xyBuffer = xyBuffer;
                    builder.UseTexture(xyBuffer, AccessFlags.Write);
                    passData.passThrough = intermediateRT;
                    builder.UseTexture(intermediateRT, AccessFlags.Write);

                    builder.SetRenderFunc(static (PassDataCIExy data, UnsafeGraphContext context) =>
                    {
                        ExecuteCIExyPrepass(CommandBufferHelpers.GetNativeCommandBuffer(context.cmd), data, data.srcColor, data.xyBuffer, data.passThrough);
                    });
                }
            }

            using (var builder = renderGraph.AddRasterRenderPass<PassDataDebugView>("Blit HDR DebugView", out var passData, profilingSampler))
            {
                passData.material = m_material;
                passData.hdrDebugMode = hdrDebugMode;
                passData.luminanceParameters = luminanceParameters;
                passData.cameraData = cameraData;

                if (requiresCIExyData)
                {
                    passData.xyBuffer = xyBuffer;
                    builder.UseTexture(xyBuffer);
                }

                passData.srcColor = srcColor;
                builder.UseTexture(srcColor);
                passData.dstColor = dstColor;
                builder.SetRenderAttachment(dstColor, 0, AccessFlags.WriteAll);

                if (overlayUITexture.IsValid())
                {
                    builder.UseTexture(overlayUITexture);
                }

                builder.SetRenderFunc(static (PassDataDebugView data, RasterGraphContext context) =>
                {
                    data.material.enabledKeywords = null;
                    Vector4 scaleBias = RenderingUtils.GetFinalBlitScaleBias(in context, in data.srcColor, in data.dstColor);
                    ExecuteHDRDebugViewFinalPass(context.cmd, in data, data.srcColor, scaleBias, data.dstColor,  data.xyBuffer);
                });
            }
        }

        internal class ShaderConstants
        {
            public static readonly int _DebugHDRModeId = Shader.PropertyToID("_DebugHDRMode");
            public static readonly int _HDRDebugParamsId = Shader.PropertyToID("_HDRDebugParams");
            public static readonly int _xyTextureId = Shader.PropertyToID("_xyBuffer");
            public static readonly int _SizeOfHDRXYMapping = 512;
            public static readonly int _CIExyUAVIndex = 1;
        }
    }
}