using System.Collections;
using System.Collections.Generic;
using System.Runtime;
using System.Runtime.InteropServices;
using System.Linq;
using System;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif

namespace UnityEngine.Rendering.Universal
{
    public class SLZGlobals
    {


        static SLZGlobals s_Instance;
        // Blue Noise
        private ComputeBuffer BlueNoiseCB;
        private ComputeBuffer HiZDimBuffer;
        private float[] BlueNoiseDim; // width, height, depth, current slice index 
        private bool hasSetBNTextures;
#if UNITY_EDITOR
        private static long framecount = 0;
        private static double timeSinceStartup = 0.0;
#endif
        //private int HiZDimBufferID = Shader.PropertyToID("HiZDimBuffer");
        private int HiZMipNumID = Shader.PropertyToID("_HiZHighestMip");
        private int HiZDimID = Shader.PropertyToID("_HiZDim");
        private int SSRConstantsID = Shader.PropertyToID("SSRConstants");
        private int CameraOpaqueTextureID = Shader.PropertyToID("_CameraOpaqueTexture");
        private int PrevHiZ0TextureID = Shader.PropertyToID("_PrevHiZ0Texture");
        public int opaqueTexID { get { return CameraOpaqueTextureID; } }
        public int prevHiZTexID { get { return PrevHiZ0TextureID; } }

        public GlobalKeyword HiZEnabledKW { get; private set; }
        public GlobalKeyword HiZMinMaxKW { get; private set; }
        public GlobalKeyword SSREnabledKW { get; private set; }

        public SLZPerCameraRTStorage PerCameraOpaque;
        public SLZPerCameraRTStorage PerCameraPrevHiZ;

        private ComputeBuffer SSRGlobalCB;

        private double extraSmoothedDT = 0.01111;

        
        private SLZGlobals()
        {
            BlueNoiseCB = new ComputeBuffer(8, sizeof(float), ComputeBufferType.Constant);
            BlueNoiseDim = new float[8];
            hasSetBNTextures = false;
            SSRGlobalCB = new ComputeBuffer(2, 4*sizeof(float), ComputeBufferType.Constant);
            HiZDimBuffer = new ComputeBuffer(15, Marshal.SizeOf<Vector4>());
            SSREnabledKW = GlobalKeyword.Create("_SLZ_SSR_ENABLED");
            HiZEnabledKW = GlobalKeyword.Create("_HIZ_ENABLED");
            HiZMinMaxKW = GlobalKeyword.Create("_HIZ_MIN_MAX_ENABLED");
            PerCameraOpaque = new SLZPerCameraRTStorage();
            PerCameraPrevHiZ = new SLZPerCameraRTStorage();
            
        }
        public static SLZGlobals instance
        {
            get
            {
                if (s_Instance == null)
                {
                    s_Instance = new SLZGlobals();
                }
                return s_Instance;
            }

        }

        public void SetHiZSSRKeyWords(bool enableSSR, bool requireHiZ, bool requireMinMax)
        {
            Shader.SetKeyword(SSREnabledKW, enableSSR);
            Shader.SetKeyword(HiZEnabledKW, requireHiZ);
            Shader.SetKeyword(HiZMinMaxKW, requireMinMax);
        }

        public void SetHiZGlobal(int numMips, Vector4 dim)
        {
            //HiZDimBuffer.SetData(data);
            //Shader.SetGlobalBuffer(HiZDimBufferID, HiZDimBuffer);
            Shader.SetGlobalInt(HiZMipNumID, numMips);
            Shader.SetGlobalVector(HiZDimID, dim);
            //Shader.SetKeyword(HiZMinMaxKW, minmax);
        }

        public void SetSSRGlobals(int maxSteps, int minMip, float hitRadius, float temporalWeight, float fov, int screenHeight)
        {
            /*
             * 0 float _SSRHitRadius;
             * 1 float _SSREdgeFade;
             * 2 int _SSRSteps;
             * 3 none
             */
            Span<float> SSRGlobalArray = stackalloc float[8];
            //SSRGlobalArray[0] = 1.0f / (1.0f + hitRadius);//hitRadius;
            //SSRGlobalArray[1] = -cameraNear / (cameraFar - cameraNear) * (hitRadius * SSRGlobalArray[0]);
            SSRGlobalArray[0] = hitRadius;
            extraSmoothedDT = 0.95 * extraSmoothedDT + 0.05 * Time.smoothDeltaTime;
            float framerateConst = 1.0f / (float)extraSmoothedDT * (1.0f / 90.0f);
            float expConst = Mathf.Exp(-framerateConst);
            float FRTemporal = (Mathf.Exp(-framerateConst * temporalWeight) - expConst) * (1.0f / (1.0f - expConst));
            //Debug.Log(FRTemporal);
            SSRGlobalArray[1] = Mathf.Clamp(1.0f - temporalWeight, 0.0078f, 1.0f); //Mathf.Clamp(FRTemporal, 0.0078f, 1.0f); 
            SSRGlobalArray[2] = maxSteps;
            SSRGlobalArray[3] = BitConverter.Int32BitsToSingle(minMip);
            float halfTan = Mathf.Tan(Mathf.Deg2Rad * (fov * 0.5f));
            SSRGlobalArray[4] = halfTan / (0.5f * (float)screenHeight); // rcp(0.5*_ScreenParams.y * UNITY_MATRIX_P._m11)
            SSRGlobalCB.SetData<float>(SSRGlobalArray);
            Shader.SetGlobalConstantBuffer(SSRConstantsID, SSRGlobalCB, 0, 8 * sizeof(float));
        }

        public void SetSSRGlobalsCmd(ref CommandBuffer cmd, int maxSteps, int minMip, float hitRadius, float temporalWeight, float fov, int screenHeight)
        {

            Span<float> SSRGlobalArray = stackalloc float[8];
            //SSRGlobalArray[0] = 1.0f / (1.0f + hitRadius);//hitRadius;
            //SSRGlobalArray[1] = -cameraNear / (cameraFar - cameraNear) * (hitRadius * SSRGlobalArray[0]);
            SSRGlobalArray[0] = hitRadius;
            extraSmoothedDT = 0.95 * extraSmoothedDT + 0.05 * Time.smoothDeltaTime;
            float framerateConst = 1.0f / (float)extraSmoothedDT * (1.0f / 90.0f);
            float expConst = Mathf.Exp(-framerateConst);
            float FRTemporal = (Mathf.Exp(-framerateConst * temporalWeight) - expConst) * (1.0f / (1.0f - expConst));
            //Debug.Log(FRTemporal);
            SSRGlobalArray[1] = Mathf.Clamp(1.0f - temporalWeight, 0.0078f, 1.0f); //Mathf.Clamp(FRTemporal, 0.0078f, 1.0f); 
            SSRGlobalArray[2] = maxSteps;
            SSRGlobalArray[3] = BitConverter.Int32BitsToSingle(minMip);
            float halfTan = Mathf.Tan(Mathf.Deg2Rad * (fov * 0.5f));
            SSRGlobalArray[4] = halfTan / (0.5f * (float)screenHeight); // rcp(0.5*_ScreenParams.y * UNITY_MATRIX_P._m11)
            cmd.SetGlobalConstantBuffer(SSRGlobalCB, SSRConstantsID, 0, 8 * sizeof(float));
        }


        public void SetBlueNoiseGlobals(Texture2DArray BlueNoiseRGBA, Texture2DArray BlueNoiseR)
        {
            if (BlueNoiseRGBA != null)
            {
                BlueNoiseDim[0] = BlueNoiseRGBA.width;
                BlueNoiseDim[1] = BlueNoiseRGBA.height;
                BlueNoiseDim[2] = BlueNoiseRGBA.depth;
                BlueNoiseDim[3] = (float) Random.Range(0, BlueNoiseRGBA.width);
                BlueNoiseDim[4] = (float) Random.Range(0, BlueNoiseRGBA.height);
#if UNITY_EDITOR
                if (!EditorApplication.isPlaying)
                {
                    if (timeSinceStartup != EditorApplication.timeSinceStartup)
                    {
                        timeSinceStartup = EditorApplication.timeSinceStartup;
                        framecount++;
                    }
                    BlueNoiseDim[3] = (int)(framecount % BlueNoiseRGBA.depth);
                    //Debug.Log(BlueNoiseDim[3]);
                }
                else
#endif
                {
                    BlueNoiseDim[3] = Mathf.Abs((int)(Time.renderedFrameCount % BlueNoiseRGBA.depth));
                }
                if (BlueNoiseCB != null)
                {
                    BlueNoiseCB.SetData(BlueNoiseDim);
                    Shader.SetGlobalConstantBuffer("BlueNoiseDim", BlueNoiseCB, 0, 16);
                }
                if (!hasSetBNTextures)
                {
                    Shader.SetGlobalTexture("_BlueNoiseRGBA", BlueNoiseRGBA);
                    Shader.SetGlobalTexture("_BlueNoiseR", BlueNoiseR);
                    hasSetBNTextures = true;
                }
            }
        }

        public void UpdateBlueNoiseFrame()
        {
            if (BlueNoiseCB != null)
            {
                long depth = (long)BlueNoiseDim[2];
#if UNITY_EDITOR
                if (!EditorApplication.isPlaying)
                {
                    BlueNoiseDim[3] = (int)((Screen.currentResolution.refreshRate * EditorApplication.timeSinceStartup) % depth);
                }
                else
#endif
                {
                    BlueNoiseDim[3] = (int)((Time.timeSinceLevelLoadAsDouble * Screen.currentResolution.refreshRate) % depth);
                }
                BlueNoiseCB.SetData(BlueNoiseDim);
                Shader.SetGlobalConstantBuffer("BlueNoiseDim", BlueNoiseCB, 0, 16);
            }
        }


        int purgeCounter = 0;
        const int maxCount = 900;
        public void RemoveTempRTStupid()
        {
            purgeCounter++;
            if (purgeCounter > maxCount)
            {
                PerCameraOpaque.RemoveAllNull();
                PerCameraPrevHiZ.RemoveAllNull();
                purgeCounter = 0;
            }
        }

        public static void Dispose()
        {
            if (s_Instance != null)
            {
                if (s_Instance.SSRGlobalCB != null)
                {
                    s_Instance.SSRGlobalCB.Dispose();
                    s_Instance.SSRGlobalCB = null;
                }
                if (s_Instance.BlueNoiseCB != null)
                {
                    s_Instance.BlueNoiseCB.Dispose();
                    s_Instance.BlueNoiseCB = null;
                }
                if (s_Instance.HiZDimBuffer != null)
                {
                    s_Instance.HiZDimBuffer.Dispose();
                    s_Instance.HiZDimBuffer = null;
                }
               
                if (s_Instance.PerCameraOpaque != null)
                {
                    s_Instance.PerCameraOpaque.Dispose();
                }
                if (s_Instance.PerCameraPrevHiZ != null)
                {
                    s_Instance.PerCameraPrevHiZ.Dispose();
                }
               
            }
            s_Instance = null;
        }

    }


    public class SLZGlobalsSetPass : ScriptableRenderPass
    {
        private bool enableSSR;
        private bool requireHiZ;
        private bool requireMinMax;

        private float ssrHitRadius;
        private int ssrMaxSteps;
        private int ssrMinMip;
        private float cameraNear;
        private float cameraFar;
        private Camera camera;
        private RTPermanentHandle prevOpaque;
        private RTPermanentHandle prevHiZ;
        public SLZGlobalsSetPass(RenderPassEvent evt)
        {
            renderPassEvent = evt;
        }
        public void Setup(CameraData camData)
        {
            enableSSR = camData.enableSSR;
            requireHiZ = camData.requiresDepthPyramid;
            requireMinMax = camData.requiresMinMaxDepthPyr;

            ssrHitRadius = camData.SSRHitRadius;
            ssrMaxSteps = camData.maxSSRSteps;
            ssrMinMip = camData.SSRMinMip;
            cameraNear = camData.camera.nearClipPlane;
            cameraFar = camData.camera.farClipPlane;

            prevOpaque = SLZGlobals.instance.PerCameraOpaque.GetHandle(camData.camera);
            prevHiZ = SLZGlobals.instance.PerCameraPrevHiZ.GetHandle(camData.camera);

            //Debug.Log("Setup for " + camData.camera.name);
        }

        public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)
        {
            ConfigureClear(ClearFlag.None, Color.black);
        }
        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            CommandBuffer cmd = CommandBufferPool.Get();
            Camera cam = renderingData.cameraData.camera;
            //cmd.SetGlobalTexture(SLZGlobals.instance.opaqueTexID, prevOpaque.Identifier());
            //cmd.SetGlobalTexture(SLZGlobals.instance.prevHiZTexID, Texture2D.whiteTexture);
            //cmd.SetGlobalTexture(SLZGlobals.instance.prevHiZTexID, prevHiZ.Identifier());
            //Debug.Log("Execute for " + cam.name + " " + SLZGlobals.instance.opaqueTexID + " " + prevOpaque.Identifier());
            using (new ProfilingScope(cmd, ProfilingSampler.Get(URPProfileId.SetSLZGlobals)))
            {
                
                if (enableSSR)
                {
                    // Hack to tell unity to store previous frame object to world vectors...
                    // Not used by SRP to enable motion vectors or depth but somehow still necessary :(
                    renderingData.cameraData.camera.depthTextureMode |= DepthTextureMode.MotionVectors | DepthTextureMode.Depth;
                   
                    cmd.SetGlobalTexture(SLZGlobals.instance.opaqueTexID, prevOpaque.renderTexture);
                    cmd.SetGlobalTexture(SLZGlobals.instance.prevHiZTexID, prevHiZ.renderTexture);
                }

                //SLZGlobals.instance.SetSSRGlobalsCmd(ref cmd, ssrMinMip, ssrMaxSteps, ssrHitRadius, cameraNear, cameraFar);
                cmd.SetKeyword(SLZGlobals.instance.SSREnabledKW, enableSSR);
                cmd.SetKeyword(SLZGlobals.instance.HiZEnabledKW, requireHiZ);
                cmd.SetKeyword(SLZGlobals.instance.HiZMinMaxKW, requireMinMax);
            }
            context.ExecuteCommandBuffer(cmd);
            CommandBufferPool.Release(cmd);
        }
    }
}