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

namespace Pinwheel.Griffin.StampTool
{
    [System.Serializable]
    [ExecuteInEditMode]
    public class GGeometryStamper : MonoBehaviour
    {
        public enum GStampChannel
        {
            Elevation, Visibility
        }

        [SerializeField]
        private bool enableTerrainMask = true;
        public bool EnableTerrainMask
        {
            get
            {
                return enableTerrainMask;
            }
            set
            {
                enableTerrainMask = value;
            }
        }

        [SerializeField]
        private int groupId;
        public int GroupId
        {
            get
            {
                return groupId;
            }
            set
            {
                groupId = value;
            }
        }

        [SerializeField]
        private Vector3 position;
        public Vector3 Position
        {
            get
            {
                return position;
            }
            set
            {
                position = value;
                transform.position = value;
            }
        }

        [SerializeField]
        private Quaternion rotation;
        public Quaternion Rotation
        {
            get
            {
                return rotation;
            }
            set
            {
                rotation = value;
                transform.rotation = value;
            }
        }

        [SerializeField]
        private Vector3 scale;
        public Vector3 Scale
        {
            get
            {
                return scale;
            }
            set
            {
                scale = value;
                transform.localScale = value;
            }
        }

        [SerializeField]
        private Texture2D stamp;
        public Texture2D Stamp
        {
            get
            {
                return stamp;
            }
            set
            {
                stamp = value;
            }
        }

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

        [SerializeField]
        private AnimationCurve falloff;
        public AnimationCurve Falloff
        {
            get
            {
                return falloff;
            }
            set
            {
                falloff = value;
            }
        }

        [SerializeField]
        private bool inverseStamp;
        public bool InverseStamp
        {
            get
            {
                return inverseStamp;
            }
            set
            {
                inverseStamp = value;
            }
        }

        [SerializeField]
        private bool useFalloffAsBlendFactor;
        public bool UseFalloffAsBlendFactor
        {
            get
            {
                return useFalloffAsBlendFactor;
            }
            set
            {
                useFalloffAsBlendFactor = value;
            }
        }

        [SerializeField]
        private GStampOperation operation;
        public GStampOperation Operation
        {
            get
            {
                return operation;
            }
            set
            {
                operation = value;
            }
        }

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

        [SerializeField]
        private int additionalMeshResolution;
        public int AdditionalMeshResolution
        {
            get
            {
                return additionalMeshResolution;
            }
            set
            {
                additionalMeshResolution = Mathf.Clamp(value, 0, 10);
            }
        }

        private Texture2D falloffTexture;

        public Rect Rect
        {
            get
            {
                Vector3[] quad = new Vector3[4];
                GetQuad(quad);
                Rect r = GUtilities.GetRectContainsPoints(quad);
                return r;
            }
        }

        private void Reset()
        {
            position = Vector3.zero;
            rotation = Quaternion.identity;
            scale = Vector3.one * 100;
            stamp = null;
            falloff = AnimationCurve.EaseInOut(0, 1, 1, 0);
            inverseStamp = false;
            useFalloffAsBlendFactor = true;

            operation = GStampOperation.Max;
            lerpFactor = 0.5f;
            additionalMeshResolution = 0;
        }

        public void Apply()
        {
            if (falloffTexture != null)
                Object.DestroyImmediate(falloffTexture);
            Internal_UpdateFalloffTexture();

            List<GStylizedTerrain> terrains = GUtilities.ExtractTerrainsFromOverlapTest(GCommon.OverlapTest(GroupId, GetQuad()));
            foreach (GStylizedTerrain t in terrains)
            {
                DrawOnTexture(t);
            }
            foreach(GStylizedTerrain t in terrains)
            {
                UpdateTerrain(t);
            }
            foreach (GStylizedTerrain t in terrains)
            {
                t.MatchEdges();
            }
        }

        private void DrawOnTexture(GStylizedTerrain t)
        {
            if (t.TerrainData == null)
                return;
            int heightMapResolution = t.TerrainData.Geometry.HeightMapResolution;
            RenderTexture rt = new RenderTexture(heightMapResolution, heightMapResolution, 0, GGeometry.HeightMapRTFormat, RenderTextureReadWrite.Linear);
            Internal_DrawOnTexture(t, rt);

            Color[] oldHeightMapColors = t.TerrainData.Geometry.HeightMap.GetPixels();
            RenderTexture.active = rt;
            t.TerrainData.Geometry.HeightMap.ReadPixels(new Rect(0, 0, heightMapResolution, heightMapResolution), 0, 0);
            t.TerrainData.Geometry.HeightMap.Apply();
            RenderTexture.active = null;
            Color[] newHeightMapColors = t.TerrainData.Geometry.HeightMap.GetPixels();

            rt.Release();
            Object.DestroyImmediate(rt);

            List<Rect> dirtyRects = new List<Rect>(GCommon.CompareTerrainTexture(t.TerrainData.Geometry.ChunkGridSize, oldHeightMapColors, newHeightMapColors));
            for (int i = 0; i < dirtyRects.Count; ++i)
            {
                t.TerrainData.Geometry.SetRegionDirty(dirtyRects[i]);
                t.TerrainData.Foliage.SetTreeRegionDirty(dirtyRects[i]);
                t.TerrainData.Foliage.SetGrassRegionDirty(dirtyRects[i]);
            }            
        }

        private void UpdateTerrain(GStylizedTerrain t)
        {
            t.TerrainData.SetDirty(GTerrainData.DirtyFlags.Geometry);
            t.UpdateTreesPosition();
            t.UpdateGrassPatches();
            t.TerrainData.Foliage.ClearTreeDirtyRegions();
            t.TerrainData.Foliage.ClearGrassDirtyRegions();
            t.TerrainData.SetDirty(GTerrainData.DirtyFlags.Foliage);
        }

        public void Internal_DrawOnTexture(GStylizedTerrain t, RenderTexture rt)
        {
            GCommon.CopyToRT(t.TerrainData.Geometry.HeightMap, rt);

            Vector3[] worldCorner = GetQuad();
            Vector2[] uvCorners = new Vector2[worldCorner.Length];
            for (int i = 0; i < uvCorners.Length; ++i)
            {
                uvCorners[i] = t.WorldPointToUV(worldCorner[i]);
            }

            Rect dirtyRect = GUtilities.GetRectContainsPoints(uvCorners);
            if (!dirtyRect.Overlaps(new Rect(0, 0, 1, 1)))
                return;

            Vector3 normalizedPos = t.WorldPointToNormalized(Position);
            float stampHeight = GUtilities.InverseLerpUnclamped(0, t.TerrainData.Geometry.Height, Scale.y);

            Material mat = GInternalMaterials.StamperMaterial;
            mat.SetTexture("_HeightMap", t.TerrainData.Geometry.HeightMap);
            mat.SetTexture("_Stamp", Stamp);
            mat.SetTexture("_Falloff", falloffTexture);
            mat.SetInt("_Operation", (int)Operation);
            mat.SetFloat("_LerpFactor", LerpFactor);
            mat.SetFloat("_StampHeight", stampHeight);
            mat.SetFloat("_StampPositionY", normalizedPos.y);
            mat.SetFloat("_Inverse", InverseStamp ? 1 : 0);
            mat.SetFloat("_UseFalloffAsBlendFactor", UseFalloffAsBlendFactor ? 1 : 0);
            mat.SetFloat("_AdditionalMeshResolution", GCommon.SUB_DIV_STEP * AdditionalMeshResolution);
            if (EnableTerrainMask)
            {
                mat.SetTexture("_TerrainMask", t.TerrainData.Mask.MaskMapOrDefault);
            }
            else
            {
                mat.SetTexture("_TerrainMask", Texture2D.blackTexture);
            }

            int pass = (int)Channel;
            GCommon.DrawQuad(rt, uvCorners, mat, pass);
        }

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

        public Vector3[] GetQuad()
        {
            Matrix4x4 matrix = Matrix4x4.TRS(Position, Rotation, Scale);
            Vector3[] quad = new Vector3[4]
            {
                matrix.MultiplyPoint(new Vector3(-0.5f, 0, -0.5f)),
                matrix.MultiplyPoint(new Vector3(-0.5f, 0, 0.5f)),
                matrix.MultiplyPoint(new Vector3(0.5f, 0, 0.5f)),
                matrix.MultiplyPoint(new Vector3(0.5f, 0, -0.5f))
            };

            return quad;
        }

        public void GetQuad(Vector3[] quad)
        {
            Matrix4x4 matrix = Matrix4x4.TRS(Position, Rotation, Scale);
            quad[0] = matrix.MultiplyPoint(new Vector3(-0.5f, 0, -0.5f));
            quad[1] = matrix.MultiplyPoint(new Vector3(-0.5f, 0, 0.5f));
            quad[2] = matrix.MultiplyPoint(new Vector3(0.5f, 0, 0.5f));
            quad[3] = matrix.MultiplyPoint(new Vector3(0.5f, 0, -0.5f));
        }

        public void GetBox(Vector3[] box)
        {
            Matrix4x4 matrix = Matrix4x4.TRS(Position, Rotation, Scale);
            box[0] = matrix.MultiplyPoint(new Vector3(-0.5f, 0, -0.5f));
            box[1] = matrix.MultiplyPoint(new Vector3(-0.5f, 0, 0.5f));
            box[2] = matrix.MultiplyPoint(new Vector3(0.5f, 0, 0.5f));
            box[3] = matrix.MultiplyPoint(new Vector3(0.5f, 0, -0.5f));
            box[4] = matrix.MultiplyPoint(new Vector3(-0.5f, 1, -0.5f));
            box[5] = matrix.MultiplyPoint(new Vector3(-0.5f, 1, 0.5f));
            box[6] = matrix.MultiplyPoint(new Vector3(0.5f, 1, 0.5f));
            box[7] = matrix.MultiplyPoint(new Vector3(0.5f, 1, -0.5f));
        }
    }
}
#endif