
using System;
using System.Collections.Generic;

namespace UnityEngine.Rendering.Universal
{
    internal class PostProcessUrpPass : ScriptableRenderPass
    {
        public Material material;
        public FilterMode filterMode { get; set; }

        private RenderTargetIdentifier source;
        private RenderTargetIdentifier tempCopy = new RenderTargetIdentifier(tempCopyString);

        private bool maskSet = false;
        private string tag;
        private string cmdTag = "Fast Post Process URP";

        private Matrix4x4 m_PrevViewProjM = Matrix4x4.identity;

        static readonly int blurTexString = Shader.PropertyToID("_BlurTex");
        static readonly int maskTextureString = Shader.PropertyToID("_MaskTex");
        static readonly int blurAmountString = Shader.PropertyToID("_BlurAmount");
        static readonly int bloomColorString = Shader.PropertyToID("_BloomColor");
        static readonly int blAmountString = Shader.PropertyToID("_BloomAmount");
        static readonly int blDiffuseString = Shader.PropertyToID("_BloomDiffuse");
        static readonly int blThresholdString = Shader.PropertyToID("_BloomThreshold");
        static readonly int BlClampString = Shader.PropertyToID("_BloomClamp");
        static readonly int BlScatterString = Shader.PropertyToID("_BloomScatter");

        // Fxaa
        static readonly int fxaaThresholdMinString = Shader.PropertyToID("_FxaaThresholdMin");
        static readonly int fxaaThresholdString = Shader.PropertyToID("_FxaaThreshold");
        static readonly int fxaaSharpnessString = Shader.PropertyToID("_FxaaSharpness");

        // Tonemap
        static readonly int tomemapExposureString = Shader.PropertyToID("_TomemapExposure");

        //LUT
        static readonly int lutTexture2DString = Shader.PropertyToID("_LutTex2D");
        static readonly int lutTexture3DString = Shader.PropertyToID("_LutTex3D");
        static readonly int lutAmountString = Shader.PropertyToID("_LutAmount");

        //Lift-Gamma-Gain
        static readonly int _lggLift = Shader.PropertyToID("_LGGLift");
        static readonly int _lggGamma = Shader.PropertyToID("_LGGGamma");
        static readonly int _lggGain = Shader.PropertyToID("_LGGGain");

        static readonly int colorString = Shader.PropertyToID("_Color");
        static readonly int contrastString = Shader.PropertyToID("_Contrast");
        static readonly int brightnessString = Shader.PropertyToID("_Brightness");
        static readonly int saturationString = Shader.PropertyToID("_Saturation");
        static readonly int exposureString = Shader.PropertyToID("_Exposure");
        static readonly int gammaString = Shader.PropertyToID("_Gamma");
        static readonly int centralFactorString = Shader.PropertyToID("_CentralFactor");
        static readonly int sideFactorString = Shader.PropertyToID("_SideFactor");
        static readonly int offsetString = Shader.PropertyToID("_Offset");
        static readonly int fishEyeString = Shader.PropertyToID("_FishEye");
        static readonly int lensdistortionString = Shader.PropertyToID("_LensDistortion");
        static readonly int vignetteColorString = Shader.PropertyToID("_VignetteColor");
        static readonly int vignetteAmountString = Shader.PropertyToID("_VignetteAmount");
        static readonly int vignetteSoftnessString = Shader.PropertyToID("_VignetteSoftness");
        static readonly int blurTempString = Shader.PropertyToID("_BlurTemp");
        static readonly int blurTemp1String = Shader.PropertyToID("_BlurTemp1");
        static readonly int tempCopyString = Shader.PropertyToID("_TempCopy");

        static readonly int bloomLowMipTexString = Shader.PropertyToID("_BloomTexLowMip");

        static readonly string bloomKeyword = "BLOOM";
        static readonly string blurKeyword = "BLUR";
        static readonly string fxaaKeyword = "FXAA";
        static readonly string chromaKeyword = "CHROMA";
        static readonly string tonemapKeyword = "TONEMAP";
        static readonly string lutKeyword = "LUT";
        static readonly string filterKeyword = "FILTER";
        static readonly string shaprenKeyword = "SHARPEN";
        static readonly string distortionKeyword = "DISTORTION";
        static readonly string liftgammagainKeyword = "LIFTGAMMAGAIN";
        static readonly string samplerName = "Mobile_FastPostProcessURP";

        private int previousLutDimension;
        private Texture2D previous;
        private Texture2D converted2D = null;
        private Texture3D converted3D = null;
        private float t, a;

        public static Shader ms_shader = null;
        public static Material ms_material = null;

        SunshaftRenderer m_sunShaft = new SunshaftRenderer();

        public class BloomRT
        {
            public int id;
            public RenderTargetIdentifier identifier;
        }

        public static List<BloomRT> bloomDownRTs = new List<BloomRT>();
        public static List<BloomRT> bloomUpRTs = new List<BloomRT>();
        const int BloomMaxStep = 16;
        static public void intiBloomRT()
        {
            if (bloomDownRTs.Count == 0 && bloomUpRTs.Count == 0)
            {
                for (int i = 0; i < BloomMaxStep; i++)
                {
                    BloomRT bloomDownRT = new BloomRT();
                    bloomDownRT.id = Shader.PropertyToID("_BloomDownRT" + i.ToString());
                    bloomDownRT.identifier = new RenderTargetIdentifier(bloomDownRT.id);
                    bloomDownRTs.Add(bloomDownRT);

                    BloomRT bloomUpRT = new BloomRT();
                    bloomUpRT.id = Shader.PropertyToID("_BloomUpRT" + i.ToString());
                    bloomUpRT.identifier = new RenderTargetIdentifier(bloomUpRT.id);
                    bloomUpRTs.Add(bloomUpRT);
                }
            }
        }

        public PostProcessUrpPass()
        {
            renderPassEvent = RenderPassEvent.AfterRenderingTransparents;
            initMateral();
            intiBloomRT();
        }

        static void cleanBloomRT(CommandBuffer cmd)
        {
            foreach(var rt in bloomDownRTs)
            {
                cmd.ReleaseTemporaryRT(rt.id);
            }

            foreach (var rt in bloomUpRTs)
            {
                cmd.ReleaseTemporaryRT(rt.id);
            }
        }

        static void initMateral()
        {
            if (ms_material == null)
            {
                ms_shader = Shader.Find("SupGames/Mobile/PostProcessURP");
                if (ms_shader == null)
                {
                    Debug.LogError("fastPostProcess shader load fail !");
                    return;
                }
                ms_material = new Material(ms_shader);
            }
        }

        public void Setup(RenderTargetIdentifier source)
        {
            this.source = source;
            initMateral();
            m_sunShaft.Setup();
        }

        static public FastPostProcess.PostProcessSettings ms_settings = new FastPostProcess.PostProcessSettings();
        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            var ps = FastPostProcess.GetSettings(renderingData.cameraData.camera);
            if (ps == null)
            {
                return;
            }
            else
            {
                ms_settings = ps.settings;
            }
            material = ms_material;
            tag = ms_settings.name;

            if (ms_material == null)
            {
                return;
            }

            if (tag == null)
                tag = samplerName;

            CommandBuffer cmd = CommandBufferPool.Get(cmdTag);
            RenderTextureDescriptor opaqueDesc = renderingData.cameraData.cameraTargetDescriptor;
            opaqueDesc.depthBufferBits = 0;

            cmd.GetTemporaryRT(tempCopyString, opaqueDesc, FilterMode.Bilinear);
            //cmd.CopyTexture(source, tempCopy);10/2修改，模拟器情况下CopyTexture无法复制纹理 TODO：判断模拟器的情况
            cmd.Blit(source, tempCopy);

            if (ms_settings.Bloom || ms_settings.Blur)
            {
                material.DisableKeyword(blurKeyword);
                material.DisableKeyword(bloomKeyword);
                if (ms_settings.Bloom)
                {
                    material.EnableKeyword(bloomKeyword);
                    material.SetColor(bloomColorString, ms_settings.BloomColor);
                    material.SetFloat(blAmountString, ms_settings.BloomAmount);
                    material.SetFloat(blDiffuseString, ms_settings.BloomDiffuse);

                    Vector4 threshold = new Vector4();
                    threshold.x = ms_settings.BloomThreshold;
                    //(threshold - knee, knee * 2, 0.25 / knee)
                    threshold.y = ms_settings.BloomThreshold - ms_settings.BloomSoftKnee;
                    threshold.z = 2 * ms_settings.BloomSoftKnee;
                    threshold.w = 0.25f / ms_settings.BloomSoftKnee;
                    material.SetVector(blThresholdString, threshold);
                    material.SetFloat(BlClampString, ms_settings.BloomClamp * 10);
                    material.SetFloat(BlScatterString, ms_settings.BloomScatter);
                }

                if (ms_settings.Blur)
                {  
                    material.EnableKeyword(blurKeyword);
                    material.SetFloat(blurAmountString, ms_settings.BlurAmount);
                    if (!maskSet)
                    {
                        material.SetTexture(maskTextureString, ms_settings.BlurMask);
                        maskSet = true;
                    }
                }

                if (ms_settings.BlurAmount > 0 || !ms_settings.Blur)
                {
                    int passNumber = BloomPassesCount(Screen.width, Screen.height);

                    // down sample.
                    for (int i = 0; i < passNumber; i++)
                    {
                        int scale = (int)Mathf.Pow(2, i + 1);
                        cmd.GetTemporaryRT(bloomDownRTs[i].id, Math.Max(Screen.width / scale, 1), Math.Max(Screen.height / scale, 1), 0, FilterMode.Bilinear, Experimental.Rendering.GraphicsFormat.B10G11R11_UFloatPack32);
                        cmd.GetTemporaryRT(bloomUpRTs[i].id, Math.Max(Screen.width / scale, 1), Math.Max(Screen.height / scale, 1), 0, FilterMode.Bilinear, Experimental.Rendering.GraphicsFormat.B10G11R11_UFloatPack32);
                        if (i == 0)
                        {
                            cmd.Blit(tempCopy, bloomDownRTs[i].identifier, material, 3);
                        }
                        else
                        {
                            cmd.Blit(bloomDownRTs[i-1].identifier, bloomDownRTs[i].identifier, material, 0);
                        }
                    }

                    //// up sample.
                    //for (int i = ms_settings.NumberOfPasses - 1; i > 0; i--)
                    //{
                    //    cmd.Blit(bloomDownRTs[i].identifier, bloomDownRTs[i - 1].identifier, material, 5);
                    //}

                    // Upsample (bilinear by default, HQ filtering does bicubic instead
                    for (int i = passNumber - 2; i >= 0; i--)
                    {
                        int lowMip = (i == passNumber - 2) ? bloomDownRTs[i + 1].id : bloomUpRTs[i + 1].id;
                        int highMip = bloomDownRTs[i].id;
                        int dst = bloomUpRTs[i].id;

                        cmd.SetGlobalTexture(bloomLowMipTexString, lowMip);
                        cmd.Blit(highMip, dst, material, 4);
                    }

                    cmd.SetGlobalTexture(blurTexString, bloomUpRTs[0].identifier);
                }
                else
                {
                    cmd.SetGlobalTexture(blurTexString, tempCopy);
                }
            }
            else
            {
                material.DisableKeyword(blurKeyword);
                material.DisableKeyword(bloomKeyword);
            }

            if(ms_settings.Fxaa)
            {
                material.EnableKeyword(fxaaKeyword);
                material.SetFloat(fxaaThresholdMinString, ms_settings.FxaaThresholdMin);
                material.SetFloat(fxaaThresholdString, ms_settings.FxaaThreshold);
                material.SetFloat(fxaaSharpnessString, ms_settings.FxaaSharpness);
            }
            else
            {
                material.DisableKeyword(fxaaKeyword);
            }

            // motion blur
            if (ms_settings.MotionBlur)
            {
                material.EnableKeyword("MOTIONBLUR");

                if (ms_settings.ViewMask)
                {
                    material.EnableKeyword("MOTIONBLURMASK");
                }
                else
                {
                    material.DisableKeyword("MOTIONBLURMASK");
                }

                Camera camera = renderingData.cameraData.camera;
                var proj = camera.nonJitteredProjectionMatrix;
                var view = camera.worldToCameraMatrix;
                var viewProj = proj * view;
                material.SetMatrix("_ViewProjM", viewProj);
                material.SetMatrix("_PrevViewProjM", m_PrevViewProjM);
                material.SetFloat("_MotionBlurIntensity", ms_settings.MotionBlurIntensity);
                material.SetFloat("_MotionBlurClamp", ms_settings.MotionBlurClamp);
                material.SetVector("_MotionMask", new Vector4(ms_settings.MaskCentre.x, ms_settings.MaskCentre.y, ms_settings.MaskScale.x, ms_settings.MaskScale.y));
                material.SetFloat("_MotionMaskGradient", ms_settings.MaskGradient);
                m_PrevViewProjM = viewProj;
            }
            else
            {
                material.DisableKeyword("MOTIONBLUR");
            }

            if (ms_settings.LUT && ms_settings.SourceLut != null)
            {
                isConverted();
                material.EnableKeyword(lutKeyword);
                material.SetFloat(lutAmountString, ms_settings.LutAmount);

                if (ms_settings.LutDimension == 2)
                {
                    material.SetTexture(lutTexture2DString, converted2D);
                }
                else
                {
                    material.SetTexture(lutTexture3DString, converted3D);
                }
            }
            else
            {
                material.DisableKeyword(lutKeyword);
            }

            if(ms_settings.LiftGammaGainSwitch)
            {
                var (_lift, _gamma, _gain) = UnityEngine.Rendering.ColorUtils.PrepareLiftGammaGain(ms_settings.LiftGammaGain_Lift, ms_settings.LiftGammaGain_Gamma, ms_settings.LiftGammaGain_Gain);
                material.EnableKeyword(liftgammagainKeyword);
                material.SetVector(_lggLift,_lift);
                material.SetVector(_lggGamma,_gamma);
                material.SetVector(_lggGain,_gain);
                //material.SetVector(_lggLift, LiftGammaGain_Lift);
                //material.SetVector(_lggGamma, LiftGammaGain_Gamma);
                //material.SetVector(_lggGain, LiftGammaGain_Gain);

            }
            else
            {
                material.DisableKeyword(liftgammagainKeyword);
            }

            if(ms_settings.Tonemap)
            {
                material.EnableKeyword(tonemapKeyword);
                material.SetFloat(tomemapExposureString, ms_settings.TonemapExposure);
            }
            else
            {
                material.DisableKeyword(tonemapKeyword);
            }

            if (ms_settings.ImageFiltering)
            {
                material.EnableKeyword(filterKeyword);
                material.SetColor(colorString, ms_settings.Color);
                material.SetFloat(contrastString, ms_settings.Contrast + 1f);
                material.SetFloat(brightnessString, ms_settings.Brightness * 0.5f + 0.5f);
                material.SetFloat(saturationString, ms_settings.Saturation + 1f);
                material.SetFloat(exposureString, ms_settings.Exposure);
                material.SetFloat(gammaString, ms_settings.Gamma);
                if (ms_settings.Sharpness > 0)
                {
                    material.EnableKeyword(shaprenKeyword);
                    material.SetFloat(centralFactorString, 1.0f + (3.2f * ms_settings.Sharpness));
                    material.SetFloat(sideFactorString, 0.8f * ms_settings.Sharpness);
                }
                else
                {
                    material.DisableKeyword(shaprenKeyword);
                }
            }
            else
            {
                material.DisableKeyword(filterKeyword);
                material.DisableKeyword(shaprenKeyword);
            }

            if (ms_settings.ChromaticAberration)
            {
                material.EnableKeyword(chromaKeyword);
                if (ms_settings.GlitchAmount > 0)
                {
                    t = Time.realtimeSinceStartup;
                    a = (1.0f + Mathf.Sin(t * 6.0f)) * ((0.5f + Mathf.Sin(t * 16.0f) * 0.25f)) * (0.5f + Mathf.Sin(t * 19.0f) * 0.25f) * (0.5f + Mathf.Sin(t * 27.0f) * 0.25f);
                    material.SetFloat(offsetString, 10 * ms_settings.Offset + ms_settings.GlitchAmount * Mathf.Pow(a, 3.0f) * 200);
                }
                else
                    material.SetFloat(offsetString, 10 * ms_settings.Offset);
                material.SetFloat(fishEyeString, 0.1f * ms_settings.FishEyeDistortion);
            }
            else
            {
                material.DisableKeyword(chromaKeyword);
            }

            if (ms_settings.Distortion)
            {
                material.SetFloat(lensdistortionString, -ms_settings.LensDistortion);
                material.EnableKeyword(distortionKeyword);
            }
            else
            {
                material.DisableKeyword(distortionKeyword);
            }

            if (ms_settings.Vignette)
            {
                material.SetColor(vignetteColorString, ms_settings.VignetteColor);
                material.SetFloat(vignetteAmountString, 1 - ms_settings.VignetteAmount);
                material.SetFloat(vignetteSoftnessString, 1 - ms_settings.VignetteSoftness - ms_settings.VignetteAmount);
            }
            else
            {
                material.SetFloat(vignetteAmountString, 1f);
                material.SetFloat(vignetteSoftnessString, 0.999f);
            }

            cmd.Blit(tempCopy, source, material, 2);

            if (ms_settings.bSunshaft)
            {
                cmd.Blit(source, tempCopy);
                m_sunShaft.Render(cmd, context, ref renderingData, ms_settings, tempCopy, source);
            }

            context.ExecuteCommandBuffer(cmd);
            CommandBufferPool.Release(cmd);
        }

        int BloomPassesCount(int width, int height)
        {
            // Start at half-res
            int tw = width/2;
            int th = height/2;

            // Determine the iteration count
            int maxSize = Mathf.Max(tw, th);
            int iterations = Mathf.FloorToInt(Mathf.Log(maxSize, 2f) - 1);
            int mipCount = Mathf.Clamp(iterations, 1, BloomMaxStep);

            return mipCount;
        }

        public override void FrameCleanup(CommandBuffer cmd)
        {
            cmd.ReleaseTemporaryRT(tempCopyString);
            cmd.ReleaseTemporaryRT(blurTempString);
            cmd.ReleaseTemporaryRT(blurTemp1String);
            cmd.ReleaseTemporaryRT(blurTexString);
            cleanBloomRT(cmd);
            m_sunShaft.Release(cmd);
        }

        private void isConverted()
        {
            if (previousLutDimension != ms_settings.LutDimension)
            {
                previousLutDimension = ms_settings.LutDimension;
                Convert(ms_settings.SourceLut);
                return;
            }

            if (ms_settings.SourceLut != previous)
            {
                previous = ms_settings.SourceLut;
                Convert(ms_settings.SourceLut);
            }
        }

        private void Convert2D(Texture2D temp2DTex)
        {
            Color[] color = temp2DTex.GetPixels();
            Color[] newCol = new Color[65536];

            for (int i = 0; i < 16; i++)
                for (int j = 0; j < 16; j++)
                    for (int x = 0; x < 16; x++)
                        for (int y = 0; y < 16; y++)
                        {
                            float bChannel = (i + j * 16.0f) / 16;
                            int bchIndex0 = Mathf.FloorToInt(bChannel);
                            int bchIndex1 = Mathf.Min(bchIndex0 + 1, 15);
                            float lerpFactor = bChannel - bchIndex0;
                            int index = x + (15 - y) * 256;
                            Color col1 = color[index + bchIndex0 * 16];
                            Color col2 = color[index + bchIndex1 * 16];

                            newCol[x + i * 16 + y * 256 + j * 4096] =
                                Color.Lerp(col1, col2, lerpFactor);
                        }
            if (converted2D)
                Object.DestroyImmediate(converted2D);

            converted2D = new Texture2D(256, 256, TextureFormat.ARGB32, false)
            {
                hideFlags = HideFlags.HideAndDontSave,
                wrapMode = TextureWrapMode.Clamp
            };

            converted2D.SetPixels(newCol);
            converted2D.Apply();
        }

        private void Convert3D(Texture2D temp3DTex)
        {
            var color = temp3DTex.GetPixels();
            var newCol = new Color[color.Length];

            for (int i = 0; i < 16; i++)
            {
                for (int j = 0; j < 16; j++)
                {
                    for (int k = 0; k < 16; k++)
                    {
                        int val = 16 - j - 1;
                        newCol[i + (j * 16) + (k * 256)] = color[k * 16 + i + val * 256];
                    }
                }
            }
            if (converted3D)
                Object.DestroyImmediate(converted3D);
            converted3D = new Texture3D(16, 16, 16, TextureFormat.ARGB32, false)
            {
                hideFlags = HideFlags.HideAndDontSave,
                wrapMode = TextureWrapMode.Clamp
            };
            converted3D.SetPixels(newCol);
            converted3D.Apply();
        }

        private void Convert(Texture2D source)
        {
            if (source == null)
                return;

            if (ms_settings.LutDimension == 2)
            {
                Convert2D(source);
            }
            else
            {
                Convert3D(source);
            }
        }
    }
}
