#if GRIFFIN
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Serialization;
using Unity.Collections;
using Pinwheel.Griffin.Rendering;

namespace Pinwheel.Griffin
{
    public class GFoliage : ScriptableObject, ISerializationCallbackReceiver
    {
        [SerializeField]
        private GTerrainData terrainData;
        public GTerrainData TerrainData
        {
            get
            {
                return terrainData;
            }
            internal set
            {
                terrainData = value;
            }
        }

        [SerializeField]
        private GTreePrototypeGroup trees;
        public GTreePrototypeGroup Trees
        {
            get
            {
                return trees;
            }
            set
            {
                trees = value;
            }
        }

        [SerializeField]
        private List<GTreeInstance> treeInstances;
        public List<GTreeInstance> TreeInstances
        {
            get
            {
                if (treeInstances == null)
                    treeInstances = new List<GTreeInstance>();
                return treeInstances;
            }
            set
            {
                treeInstances = value;
            }
        }

        [SerializeField]
        private GSnapMode treeSnapMode;
        public GSnapMode TreeSnapMode
        {
            get
            {
                return treeSnapMode;
            }
            set
            {
                treeSnapMode = value;
            }
        }

        [SerializeField]
        private LayerMask treeSnapLayerMask;
        public LayerMask TreeSnapLayerMask
        {
            get
            {
                return treeSnapLayerMask;
            }
            set
            {
                treeSnapLayerMask = value;
            }
        }

        [SerializeField]
        private GGrassPrototypeGroup grasses;
        public GGrassPrototypeGroup Grasses
        {
            get
            {
                return grasses;
            }
            set
            {
                GGrassPrototypeGroup oldValue = grasses;
                GGrassPrototypeGroup newValue = value;
                grasses = newValue;
                if (oldValue != newValue)
                {
                    if (TerrainData != null)
                    {
                        TerrainData.InvokeGrassPrototypeGroupChanged();
                    }
                }
            }
        }

        [SerializeField]
        private GSnapMode grassSnapMode;
        public GSnapMode GrassSnapMode
        {
            get
            {
                return grassSnapMode;
            }
            set
            {
                grassSnapMode = value;
            }
        }

        [SerializeField]
        private LayerMask grassSnapLayerMask;
        public LayerMask GrassSnapLayerMask
        {
            get
            {
                return grassSnapLayerMask;
            }
            set
            {
                grassSnapLayerMask = value;
            }
        }

        [SerializeField]
        private int patchGridSize;
        public int PatchGridSize
        {
            get
            {
                return patchGridSize;
            }
            set
            {
                int oldValue = patchGridSize;
                int newValue = Mathf.Clamp(value, 2, 20);

                patchGridSize = newValue;
                if (oldValue != newValue)
                {
                    if (grassPatches != null)
                    {
                        ResampleGrassPatches();
                    }
                    if (TerrainData != null)
                    {
                        TerrainData.InvokeGrassPatchGridSizeChange();
                    }
                }
            }
        }

        [SerializeField]
        private GGrassPatch[] grassPatches;
        public GGrassPatch[] GrassPatches
        {
            get
            {
                if (grassPatches == null)
                {
                    grassPatches = new GGrassPatch[PatchGridSize * PatchGridSize];
                    for (int x = 0; x < PatchGridSize; ++x)
                    {
                        for (int z = 0; z < patchGridSize; ++z)
                        {
                            GGrassPatch patch = new GGrassPatch(this, x, z);
                            grassPatches[GUtilities.To1DIndex(x, z, PatchGridSize)] = patch;
                        }
                    }
                }
                if (grassPatches.Length != PatchGridSize * PatchGridSize)
                {
                    ResampleGrassPatches();
                }
                return grassPatches;
            }
        }

        private List<Rect> treeDirtyRegions;
        private List<Rect> TreeDirtyRegions
        {
            get
            {
                if (treeDirtyRegions == null)
                {
                    treeDirtyRegions = new List<Rect>();
                }
                return treeDirtyRegions;
            }
            set
            {
                treeDirtyRegions = value;
            }
        }

        private List<Rect> grassDirtyRegions;
        private List<Rect> GrassDirtyRegions
        {
            get
            {
                if (grassDirtyRegions == null)
                {
                    grassDirtyRegions = new List<Rect>();
                }
                return grassDirtyRegions;
            }
            set
            {
                grassDirtyRegions = value;
            }
        }

        [SerializeField]
        private bool enableInteractiveGrass;
        public bool EnableInteractiveGrass
        {
            get
            {
                return enableInteractiveGrass;
            }
            set
            {
                enableInteractiveGrass = value;
            }
        }

        [SerializeField]
        private int vectorFieldMapResolution;
        public int VectorFieldMapResolution
        {
            get
            {
                return vectorFieldMapResolution;
            }
            set
            {
                vectorFieldMapResolution = Mathf.Clamp(Mathf.ClosestPowerOfTwo(value), GCommon.TEXTURE_SIZE_MIN, GCommon.TEXTURE_SIZE_MAX);
            }
        }

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

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

        public int GrassInstanceCount
        {
            get
            {
                int count = 0;
                for (int i = 0; i < GrassPatches.Length; ++i)
                {
                    count += GrassPatches[i].Instances.Count;
                }
                return count;
            }
        }

        public float grassVersion;

        public const float GRASS_VERSION_COMPRESSED = 2020.1f;

        public void Reset()
        {
            name = "Foliage";
            Trees = GRuntimeSettings.Instance.foliageDefault.trees;
            TreeSnapMode = GRuntimeSettings.Instance.foliageDefault.treeSnapMode;
            TreeSnapLayerMask = GRuntimeSettings.Instance.foliageDefault.treeSnapLayerMask;
            Grasses = GRuntimeSettings.Instance.foliageDefault.grasses;
            GrassSnapMode = GRuntimeSettings.Instance.foliageDefault.grassSnapMode;
            GrassSnapLayerMask = GRuntimeSettings.Instance.foliageDefault.grassSnapLayerMask;
            PatchGridSize = GRuntimeSettings.Instance.foliageDefault.patchGridSize;
            EnableInteractiveGrass = GRuntimeSettings.Instance.foliageDefault.enableInteractiveGrass;
            VectorFieldMapResolution = GRuntimeSettings.Instance.foliageDefault.vectorFieldMapResolution;
            BendSensitive = GRuntimeSettings.Instance.foliageDefault.bendSensitive;
            RestoreSensitive = GRuntimeSettings.Instance.foliageDefault.restoreSensitive;
            ClearGrassInstances();
            ClearTreeInstances();

            grassVersion = GVersionInfo.Number;
        }

        public void ResetFull()
        {
            Reset();
        }

        public void Refresh()
        {
            //if (Trees != null)
            //{
            //    List<GTreePrototype> prototypes = Trees.Prototypes;
            //    //for (int i = 0; i < prototypes.Count; ++i)
            //    //{
            //    //    prototypes[i].Refresh();
            //    //}
            //    RemoveTreeInstances(t => t.PrototypeIndex < 0 || t.PrototypeIndex >= Trees.Prototypes.Count);
            //}
            //if (Grasses != null)
            //{
            //    for (int i = 0; i < GrassPatches.Length; ++i)
            //    {
            //        GrassPatches[i].RemoveInstances(g => g.PrototypeIndex < 0 || g.PrototypeIndex >= Grasses.Prototypes.Count);
            //    }
            //}
        }

        private void ResampleGrassPatches()
        {
            List<GGrassInstance> grassInstances = new List<GGrassInstance>();
            for (int i = 0; i < grassPatches.Length; ++i)
            {
                grassInstances.AddRange(grassPatches[i].Instances);
            }

            grassPatches = new GGrassPatch[PatchGridSize * PatchGridSize];
            for (int x = 0; x < PatchGridSize; ++x)
            {
                for (int z = 0; z < patchGridSize; ++z)
                {
                    int index = GUtilities.To1DIndex(x, z, PatchGridSize);
                    GGrassPatch patch = new GGrassPatch(this, x, z);
                    grassPatches[index] = patch;
                }
            }

            AddGrassInstances(grassInstances);
        }

        public void AddGrassInstances(List<GGrassInstance> instances)
        {
            Rect[] uvRects = new Rect[GrassPatches.Length];
            for (int r = 0; r < uvRects.Length; ++r)
            {
                uvRects[r] = GrassPatches[r].GetUvRange();
            }

            bool[] dirty = new bool[GrassPatches.Length];
            for (int i = 0; i < instances.Count; ++i)
            {
                GGrassInstance grass = instances[i];
                for (int r = 0; r < uvRects.Length; ++r)
                {
                    if (uvRects[r].Contains(new Vector2(grass.position.x, grass.position.z)))
                    {
                        grassPatches[r].Instances.Add(grass);
                        dirty[r] = true;
                        break;
                    }
                }
            }

            for (int i = 0; i < dirty.Length; ++i)
            {
                if (dirty[i] == true)
                {
                    GrassPatches[i].RecalculateBounds();
                    GrassPatches[i].Changed();
                }
            }
        }

        public List<GGrassInstance> GetGrassInstances()
        {
            List<GGrassInstance> instances = new List<GGrassInstance>();
            for (int i = 0; i < GrassPatches.Length; ++i)
            {
                instances.AddRange(GrassPatches[i].Instances);
            }
            return instances;
        }

        public void ClearGrassInstances()
        {
            for (int i = 0; i < GrassPatches.Length; ++i)
            {
                GrassPatches[i].ClearInstances();
            }
        }

        public void SetTreeRegionDirty(Rect uvRect)
        {
            TreeDirtyRegions.Add(uvRect);
        }

        public void SetTreeRegionDirty(IEnumerable<Rect> uvRects)
        {
            TreeDirtyRegions.AddRange(uvRects);
        }

        public Rect[] GetTreeDirtyRegions()
        {
            return TreeDirtyRegions.ToArray();
        }

        public void ClearTreeDirtyRegions()
        {
            TreeDirtyRegions.Clear();
        }

        public void SetGrassRegionDirty(Rect uvRect)
        {
            GrassDirtyRegions.Add(uvRect);
        }

        public void SetGrassRegionDirty(IEnumerable<Rect> uvRects)
        {
            GrassDirtyRegions.AddRange(uvRects);
        }

        public Rect[] GetGrassDirtyRegions()
        {
            return GrassDirtyRegions.ToArray();
        }

        public void ClearGrassDirtyRegions()
        {
            GrassDirtyRegions.Clear();
        }

        public void CopyTo(GFoliage des)
        {
            des.Trees = Trees;
            des.TreeSnapMode = TreeSnapMode;
            des.TreeSnapLayerMask = TreeSnapLayerMask;
            des.Grasses = Grasses;
            des.GrassSnapMode = GrassSnapMode;
            des.GrassSnapLayerMask = GrassSnapLayerMask;
            des.PatchGridSize = PatchGridSize;
        }

        public void OnBeforeSerialize()
        {
            for (int i = 0; i < GrassPatches.Length; ++i)
            {
                GrassPatches[i].Serialize();
            }
        }

        public void OnAfterDeserialize()
        {
            for (int i = 0; i < GrassPatches.Length; ++i)
            {
                GrassPatches[i].Deserialize();
            }
        }

        public void Internal_UpgradeGrassSerializeVersion()
        {
            for (int i = 0; i < GrassPatches.Length; ++i)
            {
                GrassPatches[i].UpgradeSerializeVersion();
            }
            grassVersion = GVersionInfo.Number;
            Debug.Log("Successfully upgrade grass serialize to newer version!");
        }

        public void GrassAllChanged()
        {
            for (int i = 0; i < GrassPatches.Length; ++i)
            {
                GrassPatches[i].Changed();
            }
        }

        public void TreeAllChanged()
        {
            if (TerrainData != null)
            {
                TerrainData.InvokeTreeChanged();
            }
        }

        public void AddTreeInstances(IEnumerable<GTreeInstance> newInstances)
        {
            TreeInstances.AddRange(newInstances);
            TreeAllChanged();
        }

        public void RemoveTreeInstances(System.Predicate<GTreeInstance> condition)
        {
            int removedCount = TreeInstances.RemoveAll(condition);
            if (removedCount > 0)
            {
                TreeAllChanged();
            }
        }

        public void ClearTreeInstances()
        {
            TreeInstances.Clear();
            TreeAllChanged();
        }

        public int GetTreeMemStats()
        {
            return TreeInstances.Count * GTreeInstance.GetStructSize();
        }

        public int GetGrassMemStats()
        {
            int memory = 0;
            if (grassPatches != null)
            {
                for (int i = 0; i < grassPatches.Length; ++i)
                {
                    if (grassPatches[i] != null)
                    {
                        memory += grassPatches[i].GetMemStats();
                    }
                }
            }
            return memory;
        }

        public NativeArray<Vector2> GetTreesPositionArray(Allocator allocator = Allocator.TempJob)
        {
            List<GTreeInstance> trees = TreeInstances;
            int treeCount = trees.Count;
            NativeArray<Vector2> positions = new NativeArray<Vector2>(treeCount, allocator, NativeArrayOptions.UninitializedMemory);
            Vector2 pos = Vector2.zero;

            for (int i = 0; i < treeCount; ++i)
            {
                GTreeInstance t = trees[i];
                pos.Set(t.position.x, t.position.z);
                positions[i] = pos;
            }

            return positions;
        }
    }
}
#endif