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

namespace UnityEngine.Rendering.Universal
{
    internal sealed class UpscalerPostProcessPass : PostProcessPass
    {
        public const string k_UpscaledColorTargetName = "_CameraColorUpscaled";
        Texture2D[] m_BlueNoise16LTex;
        bool m_IsValid;

        public UpscalerPostProcessPass(Texture2D[] blueNoise16LTex)
        {
            this.renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing - 1;
            this.profilingSampler = null;   // Use default name
            m_BlueNoise16LTex = blueNoise16LTex;

            m_IsValid = m_BlueNoise16LTex != null && m_BlueNoise16LTex.Length > 0;
        }

        public override void Dispose()
        {
            m_IsValid = false;
        }

        public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
        {
#if ENABLE_UPSCALER_FRAMEWORK
            if (!m_IsValid)
                return;

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

            var sourceTexture = resourceData.cameraColor;
            var srcDesc = sourceTexture.GetDescriptor(renderGraph);

            // Create a context item containing upscaling inputs
            UpscalingIO io = frameData.Create<UpscalingIO>();
            io.cameraColor = sourceTexture;
            io.cameraDepth = resourceData.cameraDepth;
            io.motionVectorColor = resourceData.motionVectorColor;
            io.motionVectorDomain = UpscalingIO.MotionVectorDomain.NDC;
            io.motionVectorDirection = UpscalingIO.MotionVectorDirection.PreviousFrameToCurrentFrame;
            io.jitteredMotionVectors = false; // URP has no jittering in MVs
            // io.exposureTexture; // TODO: set exposure texture when available
            io.preExposureValue = 1.0f; // TODO: set if exposure value is pre-multiplied
            io.hdrDisplayInformation = cameraData.isHDROutputActive ? cameraData.hdrDisplayInformation : new HDROutputUtils.HDRDisplayInformation(-1, -1, -1, 160.0f);
            io.preUpscaleResolution = new Vector2Int(
                cameraData.cameraTargetDescriptor.width,
                cameraData.cameraTargetDescriptor.height
            );
            io.previousPreUpscaleResolution = io.preUpscaleResolution; // URP doesn't support Dynamic Resolution Scaling (DRS).
            io.postUpscaleResolution = new Vector2Int(cameraData.pixelWidth, cameraData.pixelHeight);
            io.motionVectorTextureSize = io.preUpscaleResolution;
            io.enableTexArray = cameraData.xr.enabled && cameraData.xr.singlePassEnabled;

            MotionVectorsPersistentData motionData = null;
            {
                cameraData.camera.TryGetComponent<UniversalAdditionalCameraData>(out var additionalCameraData);
                Debug.Assert(additionalCameraData != null);
                motionData = additionalCameraData.motionVectorsPersistentData;
                Debug.Assert(motionData != null);
            }
            io.cameraInstanceID = cameraData.camera.GetEntityId();
            io.nearClipPlane = cameraData.camera.nearClipPlane;
            io.farClipPlane = cameraData.camera.farClipPlane;
            io.fieldOfViewDegrees = cameraData.camera.fieldOfView;
            io.invertedDepth = SystemInfo.usesReversedZBuffer;
            io.flippedY = SystemInfo.graphicsUVStartsAtTop;
            io.flippedX = false;
            io.hdrInput = Experimental.Rendering.GraphicsFormatUtility.IsHDRFormat(srcDesc.format);
            io.numActiveViews = cameraData.xr.enabled ? cameraData.xr.viewCount : 1;
            io.eyeIndex = (cameraData.xr.enabled && !cameraData.xr.singlePassEnabled) ? cameraData.xr.multipassId : 0;
            io.worldSpaceCameraPositions = new Vector3[io.numActiveViews];
            io.previousWorldSpaceCameraPositions = new Vector3[io.numActiveViews];
            io.previousPreviousWorldSpaceCameraPositions = new Vector3[io.numActiveViews];
            for (int i = 0; i < io.numActiveViews; i++)
            {
                io.worldSpaceCameraPositions[i] = motionData.worldSpaceCameraPos;
                io.previousWorldSpaceCameraPositions[i] = motionData.previousWorldSpaceCameraPos;
                io.previousPreviousWorldSpaceCameraPositions[i] = motionData.previousPreviousWorldSpaceCameraPos;
            }
            io.projectionMatrices = motionData.projectionStereo;
            io.previousProjectionMatrices = motionData.previousProjectionStereo;
            io.previousPreviousProjectionMatrices = motionData.previousPreviousProjectionStereo;
            io.viewMatrices = motionData.viewStereo;
            io.previousViewMatrices = motionData.previousViewStereo;
            io.previousPreviousViewMatrices = motionData.previousPreviousViewStereo;
            io.resetHistory = cameraData.resetHistory;
            // TODO (Apoorva): Maybe we want to support this?
            // URP supports adding an offset value to the TAA frame index for testing determinism as follows:
            //     io.frameIndex = Time.frameCount + settings.jitterFrameCountOffset;
            io.frameIndex = Time.frameCount;
            io.deltaTime = motionData.deltaTime;
            io.previousDeltaTime = motionData.lastDeltaTime;
            io.blueNoiseTextureSet = m_BlueNoise16LTex;

            // The motion scaling feature is only active outside of test environments. If we allowed it to run
            // during automated graphics tests, the results of each test run would be dependent on system
            // performance.
#if LWRP_DEBUG_STATIC_POSTFX
            io.enableMotionScaling = false;
#else
            io.enableMotionScaling = true;
#endif
            io.enableHwDrs = false; // URP doesn't support hardware dynamic resolution scaling
            // Insert the active upscaler's render graph passes
            postProcessingData.activeUpscaler.RecordRenderGraph(renderGraph, frameData);

            // Update the camera resolution to reflect the upscaled size
            var dstDesc = io.cameraColor.GetDescriptor(renderGraph);
            UpdateCameraResolution(renderGraph, cameraData, new Vector2Int(dstDesc.width, dstDesc.height));

            // Use the output texture of upscaling
            resourceData.cameraColor = io.cameraColor;
#endif
        }

        private class UpdateCameraResolutionPassData
        {
            internal Vector2Int newCameraTargetSize;
        }

        // Updates render target descriptors and shader constants to reflect a new render size
        // This should be called immediately after the resolution changes mid-frame (typically after an upscaling operation).
        static internal void UpdateCameraResolution(RenderGraph renderGraph, UniversalCameraData cameraData, Vector2Int newCameraTargetSize)
        {
            // Update the camera data descriptor to reflect post-upscaled sizes
            cameraData.cameraTargetDescriptor.width = newCameraTargetSize.x;
            cameraData.cameraTargetDescriptor.height = newCameraTargetSize.y;

            // Update the shader constants to reflect the new camera resolution
            using (var builder = renderGraph.AddUnsafePass<UpdateCameraResolutionPassData>("Update Camera Resolution", out var passData))
            {
                passData.newCameraTargetSize = newCameraTargetSize;

                // This pass only modifies shader constants
                builder.AllowGlobalStateModification(true);

                // Wrap constant modification into a pass to force graph execution timeline.
                builder.SetRenderFunc(static (UpdateCameraResolutionPassData data, UnsafeGraphContext ctx) =>
                {
                    ctx.cmd.SetGlobalVector(
                        ShaderPropertyId.screenSize,
                        new Vector4(
                            data.newCameraTargetSize.x,
                            data.newCameraTargetSize.y,
                            1.0f / data.newCameraTargetSize.x,
                            1.0f / data.newCameraTargetSize.y
                        )
                    );
                });
            }
        }

        // Precomputed shader ids to same some CPU cycles (mostly affects mobile)
        public static class ShaderConstants
        {
        }
    }
}