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

namespace UnityEngine.Rendering.Universal
{
    internal sealed class MotionBlurPostProcessPass : PostProcessPass
    {
        public const string k_TargetName = "_MotionBlurTarget";

        Material m_Material;
        bool m_IsValid;

        public MotionBlurPostProcessPass(Shader shader)
        {
            this.renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing - 1;
            this.profilingSampler = null;

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

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

        private class MotionBlurPassData
        {
            internal TextureHandle sourceTexture;
            internal Material material;
            internal int passIndex;
            internal Camera camera;
            internal Experimental.Rendering.XRPass xr;
            internal float intensity;
            internal float clamp;
            internal bool enableAlphaOutput;
        }
        public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
        {
            if(!m_IsValid)
                return;

            var motionBlur = volumeStack.GetComponent<MotionBlur>();

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

            var sourceTexture = resourceData.cameraColor;
            var destinationTexture = PostProcessUtils.CreateCompatibleTexture(renderGraph, sourceTexture, k_TargetName, true, FilterMode.Bilinear);

            TextureHandle motionVectorColor = resourceData.motionVectorColor;
            TextureHandle cameraDepthTexture = resourceData.cameraDepthTexture;

            var mode = motionBlur.mode.value;
            int passIndex = (int)motionBlur.quality.value;
            passIndex += (mode == MotionBlurMode.CameraAndObjects) ? ShaderPass.k_CameraAndObjectMotionBlurLow : ShaderPass.k_CameraMotionBlurLow;

            using (var builder = renderGraph.AddRasterRenderPass<MotionBlurPassData>("Motion Blur", out var passData, ProfilingSampler.Get(URPProfileId.RG_MotionBlur)))
            {
                builder.SetRenderAttachment(destinationTexture, 0, AccessFlags.Write);
                passData.sourceTexture = sourceTexture;
                builder.UseTexture(sourceTexture, AccessFlags.Read);

                if (mode == MotionBlurMode.CameraAndObjects)
                {
                    Debug.Assert(ScriptableRenderer.current.SupportsMotionVectors(), "Current renderer does not support motion vectors.");
                    Debug.Assert(motionVectorColor.IsValid(), "Motion vectors are invalid. Per-object motion blur requires a motion vector texture.");

                    builder.UseTexture(motionVectorColor, AccessFlags.Read);
                }

                Debug.Assert(cameraDepthTexture.IsValid(), "Camera depth texture is invalid. Per-camera motion blur requires a depth texture.");
                builder.UseTexture(cameraDepthTexture, AccessFlags.Read);
                passData.material = m_Material;
                passData.passIndex = passIndex;
                passData.camera = cameraData.camera;
                passData.xr = cameraData.xr;
                passData.enableAlphaOutput = cameraData.isAlphaOutputEnabled;
                passData.intensity = motionBlur.intensity.value;
                passData.clamp = motionBlur.clamp.value;
                builder.SetRenderFunc(static (MotionBlurPassData data, RasterGraphContext context) =>
                {
                    var cmd = context.cmd;
                    RTHandle sourceTextureHdl = data.sourceTexture;

                    UpdateMotionBlurMatrices(data.material, data.camera, data.xr);

                    var sourceSize = PostProcessUtils.CalcShaderSourceSize(sourceTextureHdl);
                    data.material.SetVector(ShaderConstants._SourceSize, sourceSize);
                    data.material.SetFloat(ShaderConstants._Intensity, data.intensity);
                    data.material.SetFloat(ShaderConstants._Clamp, data.clamp);
                    CoreUtils.SetKeyword(data.material, ShaderKeywordStrings._ENABLE_ALPHA_OUTPUT, data.enableAlphaOutput);

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

            resourceData.cameraColor = destinationTexture;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void UpdateMotionBlurMatrices(Material material, Camera camera, Experimental.Rendering.XRPass xr)
        {
            MotionVectorsPersistentData motionData = null;

            if(camera.TryGetComponent<UniversalAdditionalCameraData>(out var additionalCameraData))
                motionData = additionalCameraData.motionVectorsPersistentData;

            if (motionData == null)
                return;

#if ENABLE_VR && ENABLE_XR_MODULE
            if (xr.enabled && xr.singlePassEnabled)
            {
                // pass maximum of 2 matrices per pass. Need to access into the matrix array
                var viewStartIndex = xr.viewCount * xr.multipassId;
                // Using motionData.stagingMatrixStereo as staging buffer to avoid allocation
                Array.Copy(motionData.previousViewProjectionStereo, viewStartIndex, motionData.stagingMatrixStereo, 0, xr.viewCount);
                material.SetMatrixArray(ShaderConstants._PrevViewProjMStereo, motionData.stagingMatrixStereo);
                Array.Copy(motionData.viewProjectionStereo, viewStartIndex, motionData.stagingMatrixStereo, 0, xr.viewCount);
                material.SetMatrixArray(ShaderConstants._ViewProjMStereo, motionData.stagingMatrixStereo);
            }
            else
#endif
            {
                int viewProjMIdx = 0;
#if ENABLE_VR && ENABLE_XR_MODULE
                if (xr.enabled)
                    viewProjMIdx = xr.multipassId * xr.viewCount;
#endif

                // TODO: These should be part of URP main matrix set. For now, we set them here for motion vector rendering.
                material.SetMatrix(ShaderConstants._PrevViewProjM, motionData.previousViewProjectionStereo[viewProjMIdx]);
                material.SetMatrix(ShaderConstants._ViewProjM, motionData.viewProjectionStereo[viewProjMIdx]);
            }
        }


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

            public static readonly int _Intensity = Shader.PropertyToID("_Intensity");
            public static readonly int _Clamp = Shader.PropertyToID("_Clamp");
            public static readonly int _SourceSize = Shader.PropertyToID("_SourceSize");
        }

        public static class ShaderPass
        {
            public const int k_CameraMotionBlurLow = 0;
            public const int k_CameraMotionBlurMedium = 1;
            public const int k_CameraMotionBlurHigh = 2;
            public const int k_CameraAndObjectMotionBlurLow = 3;
            public const int k_CameraAndObjectMotionBlurMedium = 4;
            public const int k_CameraAndObjectMotionBlurHigh = 5;
        }
    }
}