using System;
using System.Collections.Generic;
using X2Engine;

namespace UnityEngine.Rendering.Universal
{
    public struct AtomsphericScatteringRenderContext
    {
        public Camera camera;
        public CommandBuffer command;
        public RenderTargetIdentifier source;

    }

    /// <summary>
    /// 
    ///
    /// 
    /// </summary>
    internal class AtomsphericScatteringPass : ScriptableRenderPass
    {

        static readonly int sp_EnvGIStrength = Shader.PropertyToID("EnvGIStrength");
        
        static readonly int sp_CameraOpaqueTextured = Shader.PropertyToID("_CameraOpaqueTexture");
        static readonly int sp_CameraDepthTexture = Shader.PropertyToID("_CameraDepthTexture");
        static readonly int sp_CameraColorTextured = Shader.PropertyToID("_CameraColorTexture");
        static readonly int sp_default_depth_id = Shader.PropertyToID("_CameraDepthAttachment");

        static readonly int sp_MainTex = Shader.PropertyToID("_MainTex");
        static readonly int sp_backgroudOffset = Shader.PropertyToID("_BackgroudOffset");
        static readonly int sp_backgroudRotation = Shader.PropertyToID("_BackgroudRotation");
        static readonly string m_ProfilerTag = "Atomspheric Scattering";
        static readonly string m_tempRTName = "_CameraTempTexture";

        RenderTargetIdentifier m_Source = new RenderTargetIdentifier(sp_CameraOpaqueTextured);
        RenderTargetIdentifier m_SourceDepth = new RenderTargetIdentifier(sp_CameraDepthTexture);

        RenderTargetIdentifier m_DefaultSource = new RenderTargetIdentifier(sp_CameraColorTextured);
        RenderTargetIdentifier m_DefaultDepth = new RenderTargetIdentifier(sp_default_depth_id);

        RenderTargetHandle m_TempRT;


        private Mesh m_sunMesh = null;
        private Mesh m_FullscreenMesh = null;

        private Mesh m_starMesh = null;
        private int m_preMaxStarMag = 0;
        private float starSizeScale = 1.0f; // if modify this value that will update after click PLAY button in Editor

        private List<CombineInstance> starQuad = new List<CombineInstance>();

        Material m_material;

        public AtomsphericScatteringPass(RenderPassEvent evt)
        {
            renderPassEvent = evt;
            m_TempRT.Init(m_tempRTName);
            //m_TempRT = new RenderTargetIdentifier("_CameraOpaqueTexture");

            CreateSunAndMoonMesh();

            // TODO : destroy????
            //            if (m_sunMesh)
            //            {


            //#if UNITY_EDITOR
            //                if (!Application.isPlaying)
            //                    UnityEngine.Object.DestroyImmediate(m_sunMesh, false);
            //                else
            //#endif
            //                    UnityEngine.Object.Destroy(m_sunMesh);
            //            }

        }

        public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescripor)
        {

            RenderTextureDescriptor descriptor = cameraTextureDescripor;
            descriptor.msaaSamples = 1;
            descriptor.depthBufferBits = 0;
            cmd.GetTemporaryRT(m_TempRT.id, descriptor, FilterMode.Point);

            //ConfigureTarget(m_TempRT.Identifier());

            m_material = null;
            if (EnvironmentSystem.instance != null)
            {
                m_material = EnvironmentSystem.instance.m_atomsphericScatteringMaterail;
#if UNITY_EDITOR
                if(EnvironmentSystem.instance.m_atomsphericScatteringMaterail != null)
                    m_material = new Material(EnvironmentSystem.instance.m_atomsphericScatteringMaterail);
#endif
            }
                
        }

        //public override void Setup(ref RenderTextureDescriptor cameraTextureDescriptor,
        //    RenderTargetHandle color, RenderTargetHandle depth)
        //{
        //    m_Source = color;
        //    m_SourceDepth = depth;
        //}

        public void Setup(ref RenderTextureDescriptor cameraTextureDescriptor,
           RenderTargetIdentifier color, RenderTargetIdentifier depth)
        {
            //m_Source = color;
            //m_SourceDepth = depth;

        }

        // public void Setup(RenderTextureDescriptor baseDescriptor, RenderTargetHandle sourceHandle)
        // {
        //     m_Source = sourceHandle;

        // }

        /// <inheritdoc/>
        /// 

        static void SetRenderTarget(
          CommandBuffer cmd,
          RenderTargetIdentifier colorAttachment,
          RenderBufferLoadAction colorLoadAction,
          RenderBufferStoreAction colorStoreAction,
          ClearFlag clearFlags,
          Color clearColor,
          TextureDimension dimension)
        {
            if (dimension == TextureDimension.Tex2DArray)
                CoreUtils.SetRenderTarget(cmd, colorAttachment, clearFlags, clearColor, 0, CubemapFace.Unknown, -1);
            else
                CoreUtils.SetRenderTarget(cmd, colorAttachment, colorLoadAction, colorStoreAction, clearFlags, clearColor);
        }

        static void SetRenderTarget(
            CommandBuffer cmd,
            RenderTargetIdentifier colorAttachment,
            RenderBufferLoadAction colorLoadAction,
            RenderBufferStoreAction colorStoreAction,
            RenderTargetIdentifier depthAttachment,
            RenderBufferLoadAction depthLoadAction,
            RenderBufferStoreAction depthStoreAction,
            ClearFlag clearFlags,
            Color clearColor,
            TextureDimension dimension)
        {
            if (depthAttachment == BuiltinRenderTextureType.CameraTarget)
            {
                SetRenderTarget(cmd, colorAttachment, colorLoadAction, colorStoreAction, clearFlags, clearColor,
                    dimension);
            }
            else
            {
                if (dimension == TextureDimension.Tex2DArray)
                    CoreUtils.SetRenderTarget(cmd, colorAttachment, depthAttachment,
                        clearFlags, clearColor, 0, CubemapFace.Unknown, -1);
                else
                    CoreUtils.SetRenderTarget(cmd, colorAttachment, colorLoadAction, colorStoreAction,
                        depthAttachment, depthLoadAction, depthStoreAction, clearFlags, clearColor);
            }
        }

        void SetRenderTarget(CommandBuffer cmd, RenderTargetIdentifier colorAttachment, RenderTargetIdentifier depthAttachment, ClearFlag clearFlag, Color clearColor)
        {
            RenderBufferLoadAction colorLoadAction = ((uint)clearFlag & (uint)ClearFlag.Color) != 0 ?
                RenderBufferLoadAction.DontCare : RenderBufferLoadAction.Load;

            RenderBufferLoadAction depthLoadAction = ((uint)clearFlag & (uint)ClearFlag.Depth) != 0 ?
                RenderBufferLoadAction.DontCare : RenderBufferLoadAction.Load;

            TextureDimension dimension = TextureDimension.Tex2D;
            SetRenderTarget(cmd, colorAttachment, colorLoadAction, RenderBufferStoreAction.Store,
                depthAttachment, depthLoadAction, RenderBufferStoreAction.Store, clearFlag, clearColor, dimension);
        }

        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            CommandBuffer cmd = CommandBufferPool.Get(m_ProfilerTag);
            bool isSystemForceEnable = true;

            // TODO : ????
            if(X2Engine.EnvironmentSystem.instance == null ||
                X2Engine.EnvironmentSystem.instance.profile == null)
                cmd.SetGlobalFloat(sp_EnvGIStrength, 1.0f);

            if (m_material != null &&
                isSystemForceEnable == true &&
                X2Engine.EnvironmentSystem.instance != null &&
                X2Engine.EnvironmentSystem.instance.profile != null &&
                X2Engine.EnvironmentSystem.instance.profile.atmosphericScattering.enable == true)
            {
                Shader.EnableKeyword("SKY_FOG");

                Camera camera = renderingData.cameraData.camera;
                AtomsphericScatteringRenderContext scatteringContext;
                scatteringContext.camera = renderingData.cameraData.camera;
                scatteringContext.command = cmd;
                scatteringContext.source = m_Source;

              
                RenderSunMoonAndStar(scatteringContext);
                cmd.SetRenderTarget(m_TempRT.id);
                RenderScreenEffect(scatteringContext);
                SetRenderTarget(cmd, m_Source, BuiltinRenderTextureType.CameraTarget, ClearFlag.Color, clearColor);
                cmd.Blit(m_TempRT.id, m_DefaultSource);

                //if (m_SourceDepth.Equals(new RenderTargetIdentifier(sp_default_depth_id)))
                //{
                //    cmd.SetRenderTarget(m_Source, m_SourceDepth);
                //}
                //else
                //{
                //    cmd.SetRenderTarget(m_Source);
                //}

                cmd.SetRenderTarget(m_DefaultSource, m_DefaultDepth);
            }
            else
            {
                Shader.DisableKeyword("SKY_FOG");
                
            }
            context.ExecuteCommandBuffer(cmd);
            CommandBufferPool.Release(cmd);
        }

        public override void FrameCleanup(CommandBuffer cmd)
        {
            if (cmd == null)
                throw new ArgumentNullException("cmd");

            m_material = null;

            cmd.ReleaseTemporaryRT(m_TempRT.id);
        }

        void CreateSunAndMoonMesh()
        {
            if (m_sunMesh == null)
            {
                m_sunMesh = new Mesh();
                m_sunMesh.vertices = new Vector3[] {
                    new Vector3(-1f, -1f, 0),
                    new Vector3( 1f,  1f, 0),
                    new Vector3( 1f, -1f, 0),
                    new Vector3(-1f,  1f, 0)
                };
                m_sunMesh.uv = new Vector2[] {
                    new Vector2(0, 0),
                    new Vector2(1, 1),
                    new Vector2(1, 0),
                    new Vector2(0, 1)
                };
                m_sunMesh.triangles = new int[] { 0, 1, 2, 1, 0, 3 };
            }
        }

        Mesh fullscreenMesh
        {
            get
            {
                if (m_FullscreenMesh != null)
                    return m_FullscreenMesh;


                m_FullscreenMesh = new Mesh { name = "Scattering Fullscreen Quad" };
                if (SystemInfo.usesReversedZBuffer)
                {
                    m_FullscreenMesh.SetVertices(new List<Vector3>
                    {
                        new Vector3(-1.0f, 1.0f, 3.0f),
                        new Vector3(1.0f, 1.0f, 2.0f),
                        new Vector3(1.0f, -1.0f, 1.0f),
                        new Vector3(-1.0f, -1.0f, 0.0f)
                    });
                }
                else
                {
                    m_FullscreenMesh.SetVertices(new List<Vector3>
                    {
                        new Vector3(-1.0f, 1.0f, 0.0f),
                        new Vector3(1.0f, 1.0f, 1.0f),
                        new Vector3(1.0f, -1.0f, 2.0f),
                        new Vector3(-1.0f, -1.0f, 3.0f)
                    });
                }


                m_FullscreenMesh.SetUVs(0, new List<Vector2>
                {
                    new Vector2(0.0f, 1.0f),
                    new Vector2(1.0f, 1.0f),
                    new Vector2(1.0f, 0.0f),
                    new Vector2(0.0f, 0.0f)
                });

                m_FullscreenMesh.SetIndices(new[] { 0, 1, 2, 0, 2, 3 }, MeshTopology.Triangles, 0, false);
                m_FullscreenMesh.UploadMeshData(true);
                return m_FullscreenMesh;
            }
        }


        public void RenderSunMoonAndStar(UnityEngine.Rendering.Universal.AtomsphericScatteringRenderContext context)
        {
            //if (!shouldRender /*|| !EnvironmentSystem.instance || !EnvironmentSystem.instance.profile*/ || !m_material)
            //{
            //    return;
            //}

            Camera camera = context.camera;
            CommandBuffer command = context.command;

            CreateSunAndMoonMesh();

            bool drawNightEffect = true;
            float time = EnvironmentSystem.instance.profile.sun.timeline;
            if (6.5 < time && time < 17.5f)
                drawNightEffect = false;

            if (drawNightEffect && EnvironmentSystem.instance.profile.atmosphericScattering.enableMilkyWay)
            {
                m_material.EnableKeyword("_MILKY_WAY_ON");
                m_material.SetVector(sp_backgroudOffset,
                    new Vector3(EnvironmentSystem.instance.profile.atmosphericScattering.backgroundOffset, 0, 0));
                m_material.SetFloat(sp_backgroudRotation,
                    EnvironmentSystem.instance.profile.atmosphericScattering.backgroundRotation);
            }
            else
                m_material.DisableKeyword("_MILKY_WAY_ON");



            if (EnvironmentSystem.instance.profile.atmosphericScattering.enableStar && drawNightEffect)
            {
                if (m_starMesh)
                {
                    Matrix4x4 tans = Matrix4x4.TRS(camera.transform.position, Quaternion.identity, Vector3.one);
                    command.DrawMesh(m_starMesh, tans, m_material, 0, 3);
                }
            }

            if (EnvironmentSystem.instance.profile.atmosphericScattering.enableSunDisk)
            {
                Vector3 sunDir = EnvironmentSystem.instance.SunDirection;
                Quaternion rot = Quaternion.FromToRotation(Vector3.forward, sunDir);
                Matrix4x4 tans = Matrix4x4.TRS(camera.transform.position/* + sunDir*/, rot, Vector3.one);
                command.DrawMesh(m_sunMesh, tans, m_material, 0, 1);
            }

            if (EnvironmentSystem.instance.profile.atmosphericScattering.enableMoon && drawNightEffect)
            {
                Vector3 moonDirection = EnvironmentSystem.instance.MoonDirection;
                Quaternion rot = Quaternion.FromToRotation(Vector3.forward, moonDirection);
                Matrix4x4 tans = Matrix4x4.TRS(camera.transform.position, rot, Vector3.one);
                command.DrawMesh(m_sunMesh, tans, m_material, 0, 2);
            }
        }

        public void RenderScreenEffect(UnityEngine.Rendering.Universal.AtomsphericScatteringRenderContext context)
        {
            //if (!shouldRender/* || !EnvironmentSystem.instance || !EnvironmentSystem.instance.profile*/ || !m_material)
            //{
            //    //context.command.Blit(context.source, context.destination);
            //    return;
            //}

            context.command.SetGlobalTexture(sp_MainTex, context.source);
            context.command.DrawMesh(fullscreenMesh, Matrix4x4.identity, m_material, 0, 0);
            //FrustumCornersGraphicsBlit(source, destination, m_material, 0);
        }

        protected bool CheckSupport()
        {
            // support ImageEffects and support depth?
            if (!SystemInfo.SupportsRenderTextureFormat(RenderTextureFormat.Depth))
            {
                return false;
            }
            else
                return true;
        }




        private struct Star
        {
            public Vector3 position;
            public Vector4 color;
        }

        //bool CheckStarsMesh()
        //{
        //    var profile = EnvironmentSystem.instance.profile;
        //    if (!profile) return false;

        //    int maxStarMag = profile.atmosphericScattering.maxStarMagnitude;
        //    if (m_starMesh) return true;

        //    float starDistance = 990f; // range within the default camera far clipping plane 1000
        //    float starSize = starDistance / 100f * starSizeScale;

        //    // Load star positions and colors from file with 9110 predefined stars.
        //    TextAsset data = Resources.Load<TextAsset>("StarsData");
        //    if (data == null) return false;

        //    const int numberOfStars = 9110;
        //    var stars = new Star[numberOfStars];

        //    using (BinaryReader reader = new BinaryReader(new MemoryStream(data.bytes)))
        //    {
        //        for (int i = 0; i < numberOfStars; i++)
        //        {
        //            stars[i].position.x = reader.ReadSingle();
        //            stars[i].position.z = reader.ReadSingle();
        //            stars[i].position.y = reader.ReadSingle(); // Z-up to Y-up

        //            stars[i].position = Vector3.Scale(stars[i].position, new Vector3(-1f, 1f, -1f));

        //            stars[i].color.x = reader.ReadSingle();
        //            stars[i].color.y = reader.ReadSingle();
        //            stars[i].color.z = reader.ReadSingle();

        //            // Using Vector3.magnitude term to sort the brightness of star magnitude
        //            stars[i].color.w = new Vector3(stars[i].color.x, stars[i].color.y, stars[i].color.z).magnitude;

        //            // fix an over bright star (Sirius)?
        //            if (stars[i].color.w > 5.7)
        //            {
        //                stars[i].color = Vector4.Normalize(stars[i].color) * 0.5f;
        //            }

        //            float threshold = 2.044e-2f;    // 4096 predefined stars
        //            if (stars[i].color.w < threshold) continue;

        //            CombineInstance ci = new CombineInstance();
        //            ci.mesh = createQuad(starSize);
        //            ci.transform = BillboardMatrix(stars[i].position * starDistance);

        //            Color[] colors = { stars[i].color, stars[i].color, stars[i].color, stars[i].color };
        //            ci.mesh.colors = colors;

        //            starQuad.Add(ci);
        //        }
        //    }
        //    // -------------------------------------------
        //    // Combine Quad Meshes
        //    m_starMesh = new Mesh();
        //    m_starMesh.bounds = new Bounds(Vector3.zero, Vector3.one * 2e9f);
        //    m_starMesh.name = "stars";
        //    m_starMesh.CombineMeshes(starQuad.ToArray());
        //    // over size mesh bounds to avoid camera frustum culling for Vertex transformation in shader 
        //    m_starMesh.bounds = new Bounds(Vector3.zero, Vector3.one * 2e9f); // less than 2,147,483,648
        //    m_starMesh.hideFlags = HideFlags.HideAndDontSave;

        //    // -------------------------------------------
        //    // clean up
        //    stars = null;
        //    for (int i = 0; i < starQuad.Count; ++i)
        //    {
        //        var tmpMesh = starQuad[i].mesh;
        //        UnityEngine.Object.Destroy(tmpMesh);
        //    }

        //    starQuad.Clear();
        //    Resources.UnloadAsset(data);
        //    //System.GC.Collect();
        //    m_preMaxStarMag = maxStarMag;
        //    return true;
        //}

        private Mesh createQuad(float size)
        {

            Vector3[] Vertices =
            {
                // 4 vertexs for 2 triangles 
                new Vector3( 1, 1, 0) * size,
                new Vector3(-1, 1, 0) * size,
                new Vector3( 1,-1, 0) * size,
                new Vector3(-1,-1, 0) * size
            };

            Vector2[] uv =
            { 
                // 2 triangles uv
                new Vector2(0, 1),
                new Vector2(1, 1),
                new Vector2(0, 0),
                new Vector2(1, 0)
            };

            int[] triangles = new int[6]
            {
                // 2 triangles
                0, 2, 1,
                2, 3, 1
            };

            Mesh m = new Mesh();
            m.name = "Star temp";
            m.vertices = Vertices;
            m.uv = uv;
            m.triangles = triangles;
            m.RecalculateNormals();
            m.hideFlags = HideFlags.HideAndDontSave;
            return m;
        }

        //Billboard will facing the center origin of the GameObject pivot 
        private Matrix4x4 BillboardMatrix(Vector3 particlePosition)
        {
            Vector3 direction = particlePosition - Vector3.zero;
            direction.Normalize();

            Vector3 particleRight = Vector3.Cross(direction, Vector3.up);
            particleRight.Normalize();

            Vector3 particleUp = Vector3.Cross(particleRight, direction);

            Matrix4x4 matrix = new Matrix4x4();

            matrix.SetColumn(0, particleRight);     // right
            matrix.SetColumn(1, particleUp);        // up
            matrix.SetColumn(2, direction);         // forward
            matrix.SetColumn(3, particlePosition);  // position

            return matrix;
        }



        //bool CheckStarsMesh()
        //{
        //    var profile = EnvironmentSystem.instance.profile;
        //    if (!profile) return false;

        //    int maxStarMag = profile.atmosphericScattering.maxStarMagnitude;
        //    if (m_starMesh && maxStarMag == m_preMaxStarMag) return true;

        //    TextAsset data = Resources.Load<TextAsset>("stars");
        //    if (null == data) return false;

        //    BinaryReader reader = new BinaryReader(new MemoryStream(data.bytes));
        //    if (!IsLegalStarFile(reader)) return false;

        //    UInt32 starNum = reader.ReadUInt32();
        //    Vector3[] vb = new Vector3[starNum * 3];
        //    //List<Vector3> vb = new List<Vector3>();
        //    int[] ib = new int[starNum * 3];
        //    Vector2[] uvs = new Vector2[starNum * 3];
        //    //Color[]   colors = new Color[starNum * 3];
        //    List<Color> colors = new List<Color>();
        //    int count = 0;
        //    for (int i = 0; i < starNum; ++i)
        //    {
        //        //try
        //        {
        //            int hrNo = reader.ReadInt32();
        //            float ra = reader.ReadSingle();
        //            float dec = reader.ReadSingle();
        //            Color32 clr32 = new Color32(0, 0, 0, 0);
        //            clr32.r = reader.ReadByte();
        //            clr32.g = reader.ReadByte();
        //            clr32.b = reader.ReadByte();
        //            clr32.a = reader.ReadByte();
        //            Color clr = clr32;
        //            float mag = -1.44f + clr.a * (6.5f + 1.44f);
        //            if (mag >= maxStarMag) continue;

        //            Vector3 v;
        //            v.x = -Mathf.Cos(Mathf.Deg2Rad * dec) * Mathf.Sin(Mathf.Deg2Rad * ra * 15f);
        //            v.y = Mathf.Sin(Mathf.Deg2Rad * dec);
        //            v.z = Mathf.Cos(Mathf.Deg2Rad * dec) * Mathf.Cos(Mathf.Deg2Rad * ra * 15f);

        //            vb[count] = vb[count + 1] = vb[count + 2] = v * 100f;
        //            //vb.Add(v * 100f);
        //            //vb.Add(v * 100f);
        //            //vb.Add(v * 100f);
        //            //colors[count] = colors[count + 1] = colors[count + 2] = clr;
        //            colors.Add(clr);
        //            colors.Add(clr);
        //            colors.Add(clr);
        //            ib[count] = count;
        //            ib[count + 1] = count + 1;
        //            ib[count + 2] = count + 2;

        //            uvs[count] = new Vector2(0f, 2f);
        //            uvs[count + 1] = new Vector2(-1.74f, -1f);
        //            uvs[count + 2] = new Vector2(1.74f, -1f);

        //            count += 3;
        //        }
        //        //catch (Exception e)
        //        //{
        //        //    X2Interface.LogHelper.LogError(e.ToString());
        //        //    X2Interface.LogHelper.LogError("index:" + i.ToString());
        //        //    X2Interface.LogHelper.LogError("count:" + count.ToString());
        //        //}
        //    }
        //    m_starMesh = new Mesh();
        //    m_starMesh.bounds = new Bounds(Vector3.zero, Vector3.one * 2e9f);
        //    m_starMesh.name = "stars";
        //    m_starMesh.hideFlags = HideFlags.HideAndDontSave;
        //    m_starMesh.SetVertices(vb, count);
        //    m_starMesh.SetTriangles(ib, 0, count);
        //    m_starMesh.SetUVs(0, uvs, count);
        //    m_starMesh.SetColors(colors);

        //    m_preMaxStarMag = maxStarMag;
        //    Resources.UnloadAsset(data);
        //    return true;
        //}

        //bool IsLegalStarFile(BinaryReader reader)
        //{
        //    UInt64 tag = reader.ReadUInt64();
        //    if (tag != 0x524154535f484a59) // "YJH_STAR"
        //        return false;

        //    UInt16 version = reader.ReadUInt16();

        //    return version == 0;
        //}

        //static void FrustumCornersGraphicsBlit(RenderTexture source, RenderTexture dest, Material fxMaterial, int passNr)
        //{
        //    RenderTexture.active = dest;

        //    fxMaterial.SetTexture(sp_MainTex, source);

        //    GL.PushMatrix();
        //    GL.LoadOrtho();

        //    fxMaterial.SetPass(passNr);

        //    GL.Begin(GL.QUADS);

        //    GL.MultiTexCoord2(0, 0.0f, 0.0f);
        //    GL.Vertex3(0.0f, 0.0f, 3.0f); // BL

        //    GL.MultiTexCoord2(0, 1.0f, 0.0f);
        //    GL.Vertex3(1.0f, 0.0f, 2.0f); // BR

        //    GL.MultiTexCoord2(0, 1.0f, 1.0f);
        //    GL.Vertex3(1.0f, 1.0f, 1.0f); // TR

        //    GL.MultiTexCoord2(0, 0.0f, 1.0f);
        //    GL.Vertex3(0.0f, 1.0f, 0.0f); // TL

        //    GL.End();
        //    GL.PopMatrix();

        //}

    }
}
