using System;
using UnityEngine.Rendering.RenderGraphModule;
using System.Runtime.CompilerServices; // AggressiveInlining

namespace UnityEngine.Rendering.Universal
{
    internal sealed class FinalPostProcessPass : PostProcessPass
    {
        Material m_Material;
        Texture2D[] m_FilmGrainTextures;
        bool m_IsValid;

        public enum FilteringOperation
        {
            Linear,
            Point,
            TaaSharpening,
            FsrSharpening
        }

        Texture m_DitherTexture;
        FilteringOperation m_FilteringOperation;
        HDROutputUtils.Operation m_HdrOperations;
        bool m_ApplySrgbEncoding;
        bool m_ApplyFxaa;   // NOTE: This is used to communicate if FXAA is already done in the previous pass.
        bool m_RenderOverlayUI;

        public FinalPostProcessPass(Shader shader, Texture2D[] filmGrainTextures)
        {
            this.renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing - 1;
            this.profilingSampler = new ProfilingSampler("Blit Final Post Processing");

            m_Material = PostProcessUtils.LoadShader(shader, passName);
            m_IsValid = m_Material != null;
            m_FilmGrainTextures = filmGrainTextures;
        }

        public override void Dispose()
        {
            CoreUtils.Destroy(m_Material);
            m_IsValid = false;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void Setup(Texture ditherTexture, FilteringOperation filteringOperation, HDROutputUtils.Operation hdrOperations, bool applySrgbEncoding, bool applyFxaa, bool renderOverlayUI)
        {
            m_DitherTexture = ditherTexture;
            m_FilteringOperation = filteringOperation;
            m_HdrOperations = hdrOperations;
            m_ApplySrgbEncoding = applySrgbEncoding;
            m_ApplyFxaa = applyFxaa;
            m_RenderOverlayUI = renderOverlayUI;
        }

        private class PostProcessingFinalBlitPassData
        {
            internal TextureHandle destinationTexture;
            internal TextureHandle sourceTexture;
            internal Material material;
            internal UniversalCameraData cameraData;

            internal Tonemapping tonemapping;
            internal FilteringOperation filteringOperation;
            internal HDROutputUtils.Operation hdrOperations;

            internal UberPostProcessPass.FilmGrainParams filmGrain;
            internal UberPostProcessPass.DitheringParams dithering;

            internal bool applySrgbEncoding;
            internal bool applyFxaa;
        }
        public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
        {
            if (!m_IsValid)
                return;

            var tonemapping = volumeStack.GetComponent<Tonemapping>();
            var filmGrain = volumeStack.GetComponent<FilmGrain>();

            var cameraData = frameData.Get<UniversalCameraData>();
            var resourceData = frameData.Get<UniversalResourceData>();

            var sourceTexture = resourceData.cameraColor;
            var destinationTexture = resourceData.backBufferColor; //By definition this pass blits to the backbuffer

            // Final blit pass
            using (var builder = renderGraph.AddRasterRenderPass<PostProcessingFinalBlitPassData>(passName, out var passData, profilingSampler))
            {
                // FSR and RCAS use global state constants.
                builder.AllowGlobalStateModification(true);
                passData.destinationTexture = destinationTexture;
                builder.SetRenderAttachment(destinationTexture, 0, AccessFlags.Write);
                passData.sourceTexture = sourceTexture;
                builder.UseTexture(sourceTexture, AccessFlags.Read);

                // NOTE: Global texture. 'overlayUITexture' is bound by the DrawOffscreenUIPass pass if needed. Not in FinalPostProcessPass.
                if (m_RenderOverlayUI)
                    builder.UseTexture(resourceData.overlayUITexture, AccessFlags.Read);

                passData.material = m_Material;
                passData.cameraData = cameraData;

                passData.tonemapping = tonemapping;
                passData.filteringOperation = m_FilteringOperation;
                passData.hdrOperations = m_HdrOperations;

                passData.filmGrain.Setup(filmGrain, m_FilmGrainTextures, cameraData.pixelWidth, cameraData.pixelHeight);
                passData.dithering.Setup(m_DitherTexture, cameraData.pixelWidth, cameraData.pixelHeight);

                passData.applySrgbEncoding = m_ApplySrgbEncoding;
                passData.applyFxaa = m_ApplyFxaa;

#if ENABLE_VR && ENABLE_XR_MODULE
                if (cameraData.xr.enabled)
                {
                    // This is a screen-space pass, make sure foveated rendering is disabled for non-uniform renders
                    bool passSupportsFoveation = !Experimental.Rendering.XRSystem.foveatedRenderingCaps.HasFlag(FoveatedRenderingCaps.NonUniformRaster);
                    builder.EnableFoveatedRasterization(cameraData.xr.supportsFoveatedRendering && passSupportsFoveation);

                    // Apply MultiviewRenderRegionsCompatible flag only to the peripheral view in Quad Views
                    if (cameraData.xr.multipassId == 0)
                    {
                        builder.SetExtendedFeatureFlags(ExtendedFeatureFlags.MultiviewRenderRegionsCompatible);
                    }
                }
#endif

                builder.SetRenderFunc(static (PostProcessingFinalBlitPassData data, RasterGraphContext context) =>
                {
                    var cmd = context.cmd;
                    var material = data.material;
                    var cameraData = data.cameraData;
                    var filteringOperation = data.filteringOperation;
                    var requireHDROutput = PostProcessUtils.RequireHDROutput(cameraData);
                    var isAlphaOutputEnabled = cameraData.isAlphaOutputEnabled;
                    var applyFxaa = data.applyFxaa;
                    var hdrColorEncoding = data.hdrOperations.HasFlag(HDROutputUtils.Operation.ColorEncoding);
                    var applySrgbEncoding = data.applySrgbEncoding;
                    RTHandle sourceTextureHdl = data.sourceTexture;

                    // Clear shader keywords state
                    material.shaderKeywords = null;

                    switch (filteringOperation)
                    {

                        case FilteringOperation.Point:
                            material.EnableKeyword(ShaderKeywordStrings.PointSampling);
                        break;
                        case FilteringOperation.TaaSharpening:
                            // Reuse RCAS as a standalone sharpening filter for TAA.
                            // If FSR is enabled then it overrides the sharpening/TAA setting and we skip it.
                            material.EnableKeyword(ShaderKeywordStrings.Rcas);
                            FSRUtils.SetRcasConstantsLinear(cmd, data.cameraData.taaSettings.contrastAdaptiveSharpening); // TODO: Global state constants.
                        break;
                        case FilteringOperation.FsrSharpening:
                            // RCAS
                            // Use the override value if it's available, otherwise use the default.
                            float sharpness = data.cameraData.fsrOverrideSharpness ? data.cameraData.fsrSharpness : FSRUtils.kDefaultSharpnessLinear;

                            // Set up the parameters for the RCAS pass unless the sharpness value indicates that it wont have any effect.
                            if (data.cameraData.fsrSharpness > 0.0f)
                            {
                                // RCAS is performed during the final post blit, but we set up the parameters here for better logical grouping.
                                material.EnableKeyword(requireHDROutput ? ShaderKeywordStrings.EasuRcasAndHDRInput : ShaderKeywordStrings.Rcas);
                                FSRUtils.SetRcasConstantsLinear(cmd, sharpness);    // TODO: Global state constants.
                            }
                        break;
                        case FilteringOperation.Linear: goto default;
                        default:
                        break;
                    }

                    if (isAlphaOutputEnabled)
                        CoreUtils.SetKeyword(material, ShaderKeywordStrings._ENABLE_ALPHA_OUTPUT, isAlphaOutputEnabled);

                    if(data.filmGrain.IsActive())
                        data.filmGrain.Apply(material);

                    if(data.dithering.IsActive())
                        data.dithering.Apply(material);

                    // FXAA could have been already applied by a previous pass.
                    if (applyFxaa)
                        material.EnableKeyword(ShaderKeywordStrings.Fxaa);

                    if (applySrgbEncoding)
                        material.EnableKeyword(ShaderKeywordStrings.LinearToSRGBConversion);

                    if (hdrColorEncoding)
                    {
                        PostProcessUtils.SetupHDROutput(material, cameraData.hdrDisplayInformation, cameraData.hdrDisplayColorGamut, data.tonemapping, data.hdrOperations, cameraData.rendersOverlayUI);
                        RenderingUtils.SetupOffscreenUIViewportParams(material, ref cameraData.pixelRect, cameraData.resolveFinalTarget);
                    }

                    material.SetVector(ShaderConstants._SourceSize, PostProcessUtils.CalcShaderSourceSize(sourceTextureHdl));

                    Vector4 scaleBias = RenderingUtils.GetFinalBlitScaleBias(context, data.sourceTexture, data.destinationTexture);
                    cmd.SetViewport(data.cameraData.pixelRect);
#if ENABLE_VR && ENABLE_XR_MODULE
                    if (data.cameraData.xr.enabled && data.cameraData.xr.hasValidVisibleMesh)
                    {
                        MaterialPropertyBlock xrPropertyBlock = XRSystemUniversal.GetMaterialPropertyBlock();
                        xrPropertyBlock.SetVector(ShaderConstants._BlitScaleBias, scaleBias);
                        xrPropertyBlock.SetTexture(ShaderConstants._BlitTexture, sourceTextureHdl);

                        data.cameraData.xr.RenderVisibleMeshCustomMaterial(cmd, data.cameraData.xr.occlusionMeshScale, material, xrPropertyBlock, 1, context.GetTextureUVOrigin(in data.sourceTexture) == context.GetTextureUVOrigin(in data.destinationTexture));
                    }
                    else
#endif
                        Blitter.BlitTexture(cmd, sourceTextureHdl, scaleBias, material, 0);
                });
            }

            resourceData.SwitchActiveTexturesToBackbuffer();
        }

        // Precomputed shader ids to same some CPU cycles (mostly affects mobile)
        public static class ShaderConstants
        {
            public static readonly int _SourceSize = Shader.PropertyToID("_SourceSize");

#if ENABLE_VR && ENABLE_XR_MODULE
            public static readonly int _BlitScaleBias = Shader.PropertyToID("_BlitScaleBias");
            public static readonly int _BlitTexture = Shader.PropertyToID("_BlitTexture");
#endif
        }
    }
}