using System.Collections.Generic;
using UnityEngine.Rendering;

namespace UnityEngine.PostProcessing
{
    using Mode = BuiltinDebugViewsModel.Mode;

    public sealed class BuiltinDebugViewsComponent : PostProcessingComponentCommandBuffer<BuiltinDebugViewsModel>
    {
        static class Uniforms
        {
            internal static readonly int _DepthScale = Shader.PropertyToID("_DepthScale");
            internal static readonly int _TempRT     = Shader.PropertyToID("_TempRT");
            internal static readonly int _Opacity    = Shader.PropertyToID("_Opacity");
            internal static readonly int _MainTex    = Shader.PropertyToID("_MainTex");
            internal static readonly int _TempRT2    = Shader.PropertyToID("_TempRT2");
            internal static readonly int _Amplitude  = Shader.PropertyToID("_Amplitude");
            internal static readonly int _Scale      = Shader.PropertyToID("_Scale");
        }

        const string k_ShaderString = "Hidden/Post FX/Builtin Debug Views";

        enum Pass
        {
            Depth,
            Normals,
            MovecOpacity,
            MovecImaging,
            MovecArrows
        }

        ArrowArray m_Arrows;

        class ArrowArray
        {
            public Mesh mesh { get; private set; }

            public int columnCount { get; private set; }
            public int rowCount { get; private set; }

            public void BuildMesh(int columns, int rows)
            {
                // Base shape
                var arrow = new Vector3[6]
                {
                    new Vector3(0f, 0f, 0f),
                    new Vector3(0f, 1f, 0f),
                    new Vector3(0f, 1f, 0f),
                    new Vector3(-1f, 1f, 0f),
                    new Vector3(0f, 1f, 0f),
                    new Vector3(1f, 1f, 0f)
                };

                // make the vertex array
                int vcount = 6 * columns * rows;
                var vertices = new List<Vector3>(vcount);
                var uvs = new List<Vector2>(vcount);

                for (int iy = 0; iy < rows; iy++)
                {
                    for (int ix = 0; ix < columns; ix++)
                    {
                        var uv = new Vector2(
                                (0.5f + ix) / columns,
                                (0.5f + iy) / rows
                                );

                        for (int i = 0; i < 6; i++)
                        {
                            vertices.Add(arrow[i]);
                            uvs.Add(uv);
                        }
                    }
                }

                // make the index array
                var indices = new int[vcount];

                for (int i = 0; i < vcount; i++)
                    indices[i] = i;

                // initialize the mesh object
                mesh = new Mesh { hideFlags = HideFlags.DontSave };
                mesh.SetVertices(vertices);
                mesh.SetUVs(0, uvs);
                mesh.SetIndices(indices, MeshTopology.Lines, 0);
                mesh.UploadMeshData(true);

                // update the properties
                columnCount = columns;
                rowCount = rows;
            }

            public void Release()
            {
                GraphicsUtils.Destroy(mesh);
                mesh = null;
            }
        }

        public override bool active
        {
            get
            {
                return model.IsModeActive(Mode.Depth)
                       || model.IsModeActive(Mode.Normals)
                       || model.IsModeActive(Mode.MotionVectors);
            }
        }

        public override DepthTextureMode GetCameraFlags()
        {
            var mode = model.settings.mode;
            var flags = DepthTextureMode.None;

            switch (mode)
            {
                case Mode.Normals:
                    flags |= DepthTextureMode.DepthNormals;
                    break;
                case Mode.MotionVectors:
                    flags |= DepthTextureMode.MotionVectors | DepthTextureMode.Depth;
                    break;
                case Mode.Depth:
                    flags |= DepthTextureMode.Depth;
                    break;
            }

            return flags;
        }

        public override CameraEvent GetCameraEvent()
        {
            return model.settings.mode == Mode.MotionVectors
                   ? CameraEvent.BeforeImageEffects
                   : CameraEvent.BeforeImageEffectsOpaque;
        }

        public override string GetName()
        {
            return "Builtin Debug Views";
        }

        public override void PopulateCommandBuffer(CommandBuffer cb)
        {
            var settings = model.settings;
            var material = context.materialFactory.Get(k_ShaderString);
            material.shaderKeywords = null;

            if (context.isGBufferAvailable)
                material.EnableKeyword("SOURCE_GBUFFER");

            switch (settings.mode)
            {
                case Mode.Depth:
                    DepthPass(cb);
                    break;
                case Mode.Normals:
                    DepthNormalsPass(cb);
                    break;
                case Mode.MotionVectors:
                    MotionVectorsPass(cb);
                    break;
            }

            context.Interrupt();
        }

        void DepthPass(CommandBuffer cb)
        {
            var material = context.materialFactory.Get(k_ShaderString);
            var settings = model.settings.depth;

            cb.SetGlobalFloat(Uniforms._DepthScale, 1f / settings.scale);
            cb.Blit((Texture)null, BuiltinRenderTextureType.CameraTarget, material, (int)Pass.Depth);
        }

        void DepthNormalsPass(CommandBuffer cb)
        {
            var material = context.materialFactory.Get(k_ShaderString);
            cb.Blit((Texture)null, BuiltinRenderTextureType.CameraTarget, material, (int)Pass.Normals);
        }

        void MotionVectorsPass(CommandBuffer cb)
        {
#if UNITY_EDITOR
            // Don't render motion vectors preview when the editor is not playing as it can in some
            // cases results in ugly artifacts (i.e. when resizing the game view).
            if (!Application.isPlaying)
                return;
#endif

            var material = context.materialFactory.Get(k_ShaderString);
            var settings = model.settings.motionVectors;

            // Blit the original source image
            int tempRT = Uniforms._TempRT;
            cb.GetTemporaryRT(tempRT, context.width, context.height, 0, FilterMode.Bilinear);
            cb.SetGlobalFloat(Uniforms._Opacity, settings.sourceOpacity);
            cb.SetGlobalTexture(Uniforms._MainTex, BuiltinRenderTextureType.CameraTarget);
            cb.Blit(BuiltinRenderTextureType.CameraTarget, tempRT, material, (int)Pass.MovecOpacity);

            // Motion vectors (imaging)
            if (settings.motionImageOpacity > 0f && settings.motionImageAmplitude > 0f)
            {
                int tempRT2 = Uniforms._TempRT2;
                cb.GetTemporaryRT(tempRT2, context.width, context.height, 0, FilterMode.Bilinear);
                cb.SetGlobalFloat(Uniforms._Opacity, settings.motionImageOpacity);
                cb.SetGlobalFloat(Uniforms._Amplitude, settings.motionImageAmplitude);
                cb.SetGlobalTexture(Uniforms._MainTex, tempRT);
                cb.Blit(tempRT, tempRT2, material, (int)Pass.MovecImaging);
                cb.ReleaseTemporaryRT(tempRT);
                tempRT = tempRT2;
            }

            // Motion vectors (arrows)
            if (settings.motionVectorsOpacity > 0f && settings.motionVectorsAmplitude > 0f)
            {
                PrepareArrows();

                float sy = 1f / settings.motionVectorsResolution;
                float sx = sy * context.height / context.width;

                cb.SetGlobalVector(Uniforms._Scale, new Vector2(sx, sy));
                cb.SetGlobalFloat(Uniforms._Opacity, settings.motionVectorsOpacity);
                cb.SetGlobalFloat(Uniforms._Amplitude, settings.motionVectorsAmplitude);
                cb.DrawMesh(m_Arrows.mesh, Matrix4x4.identity, material, 0, (int)Pass.MovecArrows);
            }

            cb.SetGlobalTexture(Uniforms._MainTex, tempRT);
            cb.Blit(tempRT, BuiltinRenderTextureType.CameraTarget);
            cb.ReleaseTemporaryRT(tempRT);
        }

        void PrepareArrows()
        {
            int row = model.settings.motionVectors.motionVectorsResolution;
            int col = row * Screen.width / Screen.height;

            if (m_Arrows == null)
                m_Arrows = new ArrowArray();

            if (m_Arrows.columnCount != col || m_Arrows.rowCount != row)
            {
                m_Arrows.Release();
                m_Arrows.BuildMesh(col, row);
            }
        }

        public override void OnDisable()
        {
            if (m_Arrows != null)
                m_Arrows.Release();

            m_Arrows = null;
        }
    }
}
