#if GRIFFIN
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;

namespace Pinwheel.Griffin.SplineTool
{
    [GDisplayName("Path Painter")]
    public class GPathPainter : GSplineModifier
    {
        public enum PaintChannel
        {
            AlbedoAndMetallic, Splat
        }

        [SerializeField]
        private AnimationCurve falloff;
        public AnimationCurve Falloff
        {
            get
            {
                if (falloff == null)
                {
                    falloff = AnimationCurve.EaseInOut(0, 0, 1, 1);
                }
                return falloff;
            }
            set
            {
                falloff = value;
            }
        }

        [SerializeField]
        private PaintChannel channel;
        public PaintChannel Channel
        {
            get
            {
                return channel;
            }
            set
            {
                channel = value;
            }
        }

        [SerializeField]
        private Color color;
        public Color Color
        {
            get
            {
                return color;
            }
            set
            {
                color = value;
            }
        }

        [SerializeField]
        private float metallic;
        public float Metallic
        {
            get
            {
                return metallic;
            }
            set
            {
                metallic = Mathf.Clamp01(value);
            }
        }

        [SerializeField]
        private float smoothness;
        public float Smoothness
        {
            get
            {
                return smoothness;
            }
            set
            {
                smoothness = Mathf.Clamp01(value);
            }
        }

        [SerializeField]
        private int splatIndex;
        public int SplatIndex
        {
            get
            {
                return splatIndex;
            }
            set
            {
                splatIndex = Mathf.Max(0, value);
            }
        }

        [SerializeField]
        private Texture2D falloffNoise;
        public Texture2D FalloffNoise
        {
            get
            {
                return falloffNoise;
            }
            set
            {
                falloffNoise = value;
            }
        }

        [SerializeField]
        private Vector2 falloffNoiseSize;
        public Vector2 FalloffNoiseSize
        {
            get
            {
                return falloffNoiseSize;
            }
            set
            {
                falloffNoiseSize = value;
            }
        }

        private Texture2D falloffTexture;

        private Material applyAlbedoMaterial;
        private Material ApplyAlbedoMaterial
        {
            get
            {
                if (applyAlbedoMaterial == null)
                {
                    applyAlbedoMaterial = new Material(GRuntimeSettings.Instance.internalShaders.pathPainterAlbedoShader);
                }
                return applyAlbedoMaterial;
            }
        }

        private Material applyMetallicSmoothnessMaterial;
        private Material ApplyMetallicSmoothnessMaterial
        {
            get
            {
                if (applyMetallicSmoothnessMaterial == null)
                {
                    applyMetallicSmoothnessMaterial = new Material(GRuntimeSettings.Instance.internalShaders.pathPainterMetallicSmoothnessShader);
                }
                return applyMetallicSmoothnessMaterial;
            }
        }

        private Material applySplatMaterial;
        private Material ApplySplatMaterial
        {
            get
            {
                if (applySplatMaterial == null)
                {
                    applySplatMaterial = new Material(GRuntimeSettings.Instance.internalShaders.pathPainterSplatShader);
                }
                return applySplatMaterial;
            }
        }

        private void Reset()
        {
            SplineCreator = GetComponent<GSplineCreator>();
            Falloff = AnimationCurve.EaseInOut(0, 0, 1, 1);
            Channel = PaintChannel.AlbedoAndMetallic;
            Color = Color.white;
            Metallic = 0;
            Smoothness = 0;
        }

        public override void Apply()
        {
            if (SplineCreator == null)
                return;
            if (falloffTexture != null)
                Object.DestroyImmediate(falloffTexture);
            Internal_UpdateFalloffTexture();
            List<GOverlapTestResult> sweepTests = SplineCreator.SweepTest();
            foreach (GOverlapTestResult st in sweepTests)
            {
                if (st.IsOverlapped)
                {
                    Apply(st.Terrain);
                }
            }
        }

        private void Apply(GStylizedTerrain t)
        {
            if (t.TerrainData == null)
                return;

            if (Channel == PaintChannel.AlbedoAndMetallic)
            {
                ApplyAlbedoAndMetallic(t);
            }
            else if (Channel == PaintChannel.Splat)
            {
                ApplySplat(t);
            }
            t.TerrainData.SetDirty(GTerrainData.DirtyFlags.Shading);
        }

        private void ApplyAlbedoAndMetallic(GStylizedTerrain t)
        {
            int albedoResolution = t.TerrainData.Shading.AlbedoMapResolution;
            RenderTexture rtAlbedo = new RenderTexture(albedoResolution, albedoResolution, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Linear);
            Internal_ApplyAlbedo(t, rtAlbedo);

            RenderTexture.active = rtAlbedo;
            t.TerrainData.Shading.AlbedoMap.ReadPixels(new Rect(0, 0, albedoResolution, albedoResolution), 0, 0);
            t.TerrainData.Shading.AlbedoMap.Apply();
            RenderTexture.active = null;
            rtAlbedo.Release();
            Object.DestroyImmediate(rtAlbedo);

            int metallicResolution = t.TerrainData.Shading.MetallicMapResolution;
            RenderTexture rtMetallic = new RenderTexture(metallicResolution, metallicResolution, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Linear);
            Internal_ApplyMetallic(t, rtMetallic);

            RenderTexture.active = rtMetallic;
            t.TerrainData.Shading.MetallicMap.ReadPixels(new Rect(0, 0, metallicResolution, metallicResolution), 0, 0);
            t.TerrainData.Shading.MetallicMap.Apply();
            RenderTexture.active = null;
            rtMetallic.Release();
            Object.DestroyImmediate(rtMetallic);
        }

        private static readonly int MAIN_TEX = Shader.PropertyToID("_MainTex");
        private static readonly int FALLOFF = Shader.PropertyToID("_Falloff");
        private static readonly int FALLOFF_NOISE = Shader.PropertyToID("_FalloffNoise");
        private static readonly int TERRAIN_MASK = Shader.PropertyToID("_TerrainMask");
        private static readonly int COLOR = Shader.PropertyToID("_Color");
        private static readonly int METALLIC = Shader.PropertyToID("_Metallic");
        private static readonly int SMOOTHNESS = Shader.PropertyToID("_Smoothness");
        private static readonly int CHANNEL_INDEX = Shader.PropertyToID("_ChannelIndex");

        private Material PrepareAlbedoMat(GStylizedTerrain t, RenderTexture rtAlbedo)
        {
            GCommon.CopyToRT(t.TerrainData.Shading.AlbedoMapOrDefault, rtAlbedo);
            Material mat = ApplyAlbedoMaterial;
            mat.SetTexture(MAIN_TEX, t.TerrainData.Shading.AlbedoMapOrDefault);
            mat.SetTexture(FALLOFF, falloffTexture);
            mat.SetTexture(FALLOFF_NOISE, FalloffNoise != null ? FalloffNoise : Texture2D.blackTexture);
            mat.SetTextureScale(FALLOFF_NOISE, new Vector2(
                FalloffNoiseSize.x != 0 ? 1f / FalloffNoiseSize.x : 0,
                FalloffNoiseSize.y != 0 ? 1f / FalloffNoiseSize.y : 0));
            mat.SetTextureOffset(FALLOFF_NOISE, Vector2.zero);
            if (SplineCreator.EnableTerrainMask)
            {
                mat.SetTexture(TERRAIN_MASK, t.TerrainData.Mask.MaskMapOrDefault);
            }
            else
            {
                mat.SetTexture(TERRAIN_MASK, Texture2D.blackTexture);
            }

            mat.SetColor(COLOR, Color);
            return mat;
        }

        public void Internal_ApplyAlbedo(GStylizedTerrain t, RenderTexture rtAlbedo)
        {
            Material mat = PrepareAlbedoMat(t, rtAlbedo);
            SplineCreator.DrawOnTexture(rtAlbedo, t.Bounds, mat);
            t.TerrainData.SetDirty(GTerrainData.DirtyFlags.Shading);
        }

        private Material PrepareMetallicMat(GStylizedTerrain t, RenderTexture rtMetallic)
        {
            GCommon.CopyToRT(t.TerrainData.Shading.MetallicMapOrDefault, rtMetallic);
            Material mat = ApplyMetallicSmoothnessMaterial;
            mat.SetTexture(MAIN_TEX, t.TerrainData.Shading.MetallicMapOrDefault);
            mat.SetTexture(FALLOFF, falloffTexture);
            mat.SetTexture(FALLOFF_NOISE, FalloffNoise != null ? FalloffNoise : Texture2D.blackTexture);
            mat.SetTextureScale(FALLOFF_NOISE, new Vector2(
                FalloffNoiseSize.x != 0 ? 1f / FalloffNoiseSize.x : 0,
                FalloffNoiseSize.y != 0 ? 1f / FalloffNoiseSize.y : 0));
            mat.SetTextureOffset(FALLOFF_NOISE, Vector2.zero);
            if (SplineCreator.EnableTerrainMask)
            {
                mat.SetTexture(TERRAIN_MASK, t.TerrainData.Mask.MaskMapOrDefault);
            }
            else
            {
                mat.SetTexture(TERRAIN_MASK, Texture2D.blackTexture);
            }

            mat.SetFloat(METALLIC, Metallic);
            mat.SetFloat(SMOOTHNESS, Smoothness);
            return mat;
        }

        public void Internal_ApplyMetallic(GStylizedTerrain t, RenderTexture rtMetallic)
        {
            Material mat = PrepareMetallicMat(t, rtMetallic);
            SplineCreator.DrawOnTexture(rtMetallic, t.Bounds, mat);
            t.TerrainData.SetDirty(GTerrainData.DirtyFlags.Shading);
        }

        public void Internal_UpdateFalloffTexture()
        {
            falloffTexture = GCommon.CreateTextureFromCurve(Falloff, 256, 1);
        }

        private void ApplySplat(GStylizedTerrain t)
        {
            int splatControlResolution = t.TerrainData.Shading.SplatControlResolution;
            int controlMapCount = t.TerrainData.Shading.SplatControlMapCount;
            RenderTexture[] rtControls = new RenderTexture[controlMapCount];
            for (int i = 0; i < controlMapCount; ++i)
            {
                rtControls[i] = new RenderTexture(splatControlResolution, splatControlResolution, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Linear);
            }
            Internal_ApplySplat(t, rtControls);
            for (int i = 0; i < controlMapCount; ++i)
            {
                Texture2D splatControl = t.TerrainData.Shading.GetSplatControl(i);
                RenderTexture.active = rtControls[i];
                splatControl.ReadPixels(new Rect(0, 0, splatControlResolution, splatControlResolution), 0, 0);
                splatControl.Apply();
                RenderTexture.active = null;

                rtControls[i].Release();
                Object.DestroyImmediate(rtControls[i]);
            }
        }

        private Material PrepareSplatMat(GStylizedTerrain t)
        {
            Material mat = ApplySplatMaterial;
            mat.SetTexture(FALLOFF, falloffTexture);
            mat.SetTexture(FALLOFF_NOISE, FalloffNoise != null ? FalloffNoise : Texture2D.blackTexture);
            mat.SetTextureScale(FALLOFF_NOISE, new Vector2(
                FalloffNoiseSize.x != 0 ? 1f / FalloffNoiseSize.x : 0,
                FalloffNoiseSize.y != 0 ? 1f / FalloffNoiseSize.y : 0));
            mat.SetTextureOffset(FALLOFF_NOISE, Vector2.zero);
            if (SplineCreator.EnableTerrainMask)
            {
                mat.SetTexture(TERRAIN_MASK, t.TerrainData.Mask.MaskMapOrDefault);
            }
            else
            {
                mat.SetTexture(TERRAIN_MASK, Texture2D.blackTexture);
            }
            return mat;
        }

        public void Internal_ApplySplat(GStylizedTerrain t, RenderTexture[] rtControls)
        {
            Material mat = PrepareSplatMat(t);

            for (int i = 0; i < rtControls.Length; ++i)
            {
                Texture2D splatControl = t.TerrainData.Shading.GetSplatControlOrDefault(i);
                GCommon.CopyToRT(splatControl, rtControls[i]);
                mat.SetTexture(MAIN_TEX, splatControl);
                if (SplatIndex / 4 == i)
                {
                    mat.SetInt(CHANNEL_INDEX, SplatIndex % 4);
                }
                else
                {
                    mat.SetInt(CHANNEL_INDEX, -1);
                }
                SplineCreator.DrawOnTexture(rtControls[i], t.Bounds, mat);
            }

            t.TerrainData.SetDirty(GTerrainData.DirtyFlags.Shading);
        }

        public void Internal_ApplyAlbedo(GStylizedTerrain t, RenderTexture rtAlbedo, ScriptableRenderContext context)
        {
            Material mat = PrepareAlbedoMat(t, rtAlbedo);
            SplineCreator.DrawOnTexture(rtAlbedo, t.Bounds, mat, context);
            t.TerrainData.SetDirty(GTerrainData.DirtyFlags.Shading);
        }

        public void Internal_ApplyMetallic(GStylizedTerrain t, RenderTexture rtMetallic, ScriptableRenderContext context)
        {
            Material mat = PrepareMetallicMat(t, rtMetallic);
            SplineCreator.DrawOnTexture(rtMetallic, t.Bounds, mat, context);
            t.TerrainData.SetDirty(GTerrainData.DirtyFlags.Shading);
        }

        public void Internal_ApplySplat(GStylizedTerrain t, RenderTexture[] rtControls, ScriptableRenderContext context)
        {
            Material mat = PrepareSplatMat(t);

            for (int i = 0; i < rtControls.Length; ++i)
            {
                Texture2D splatControl = t.TerrainData.Shading.GetSplatControlOrDefault(i);
                GCommon.CopyToRT(splatControl, rtControls[i]);
                mat.SetTexture(MAIN_TEX, splatControl);
                if (SplatIndex / 4 == i)
                {
                    mat.SetInt(CHANNEL_INDEX, SplatIndex % 4);
                }
                else
                {
                    mat.SetInt(CHANNEL_INDEX, -1);
                }
                SplineCreator.DrawOnTexture(rtControls[i], t.Bounds, mat, context);
            }

            t.TerrainData.SetDirty(GTerrainData.DirtyFlags.Shading);
        }
    }
}
#endif