namespace DeepSpaceLabs.SAM
{
    using DeepSpaceLabs.Core;
    using JBooth.MicroVerseCore;
    using System.Collections.Generic;
    using UnityEngine;

    [AddComponentMenu("Deep Space Labs/SAM/3rd Party/MicroVerse/Road Asset Chunk Manager")]
    public class RoadAssetChunkManager : WorldGroupingListener
    {
        [SerializeField, FieldRename("MicroVerse Grouping*", "MicroVerse Grouping\n\nThe World Grouping that contains the MicroVerse root Asset Chunks\n\nThese are the Asset Chunks that contain the actual Road System scripts!\n\nRemember that this Road Configurer needs to be added to both that Grouping as well as the Grouping which has been configured for the Road/Intersection mesh Asset Chunks.")]
        int microverseGrouping;

        [SerializeField, FieldRename("Road System Name*", "Road System Name\n\nThe exact name of the game object that contains the Road System whose road/intersection meshes have been converted to Asset Chunks. At runtime this Configurer will search the Asset Chunk containing the MicroVerse hierarchy (as set via the 'MicroVerse Grouping' setting) to find this Road System. It will then store a reference to it, disable the 'Generate At Load' property to avoid runtime generation of the road/intersection meshes, and finally destroy all child game objects under the Road System (since they are not needed at runtime).")]
        string roadSystemName;

        [SerializeField, FieldRename("Enable Colliders*", "Enable Colliders\n\nMicroVerse usually disables the colliders of the procedurally generated road/instersection meshes before they are saved to a scene. As such, it is usually necessary to enable the colliders each time the roads/intersections are activated, however if you have manually enabled the colliders and saved the scenes the meshes are in, you can disable this option to save some CPU cycles.")]
        bool enableColliders = true;

        [SerializeField, FieldRename("Colliders To Enable\nPer Batch*", "enableColliders", true, "Colliders To Enable Per Batch\n\nThe Configurerer will enable this many colliders and then query the World's Execution Controller to determine if it should keep executing. If it should, it will enable another batch of collideres and then query it again, and so on, until the Execution Controllers says to stop executing, at which point it will yield for a frame.\n\nTo enable all colliders in a single frame, set this value to 0.")]
        int collidersToEnablePerBatch = 10;

        [SerializeField, FieldRename("Road Material Overrides\nTo Process Per Batch*", "Road Material Overrides To Process Per Batch\n\nThe Configurerer will process this many Road Material Overrides (which involves setting the material and then destroying the RoadMaterialOverride component) and then query the World's Execution Controller to determine if it should keep executing. If it should, it will process another batch of overrides and then query it again, and so on, until the Execution Controllers says to stop executing, at which point it will yield for a frame.\n\nTo process all overrides in a single frame, set this value to 0.")]
        int overridesToProcessPerBatch = 10;

        [SerializeField, FieldRename("Road System Children\nTo Destroy Per Batch*", "Road System Children To Destroy Per Batch\n\nWhen loading the Micro Verse Asset Chunk, which contains all Road Systems, the children of the road system indicated by 'Road System Name' are destroyed, since they are not needed.\n\nThe Configurerer will destroy this many children and then query the World's Execution Controller to determine if it should keep executing. If it should, it will destroy another batch of children and then query it again, and so on, until the Execution Controllers says to stop executing, at which point it will yield for a frame.\n\nTo destroy all children in a single frame, set this value to 0.")]
        int roadSystemChildrenToDestroyPerBatch = 10;

        Dictionary<Cell, RoadSystem> cellRoadSystems = new Dictionary<Cell, RoadSystem>();

        List<Collider> colliders = new List<Collider>();
        List<RoadMaterialOverride> roadMaterialOverrides = new List<RoadMaterialOverride>();

        public override bool IgnoreLODTransitions => false;

        public override ListenerYieldBehavior YieldBehaviorAfterMethodExecution => ListenerYieldBehavior.YieldOrContinue;

        private void OnEnable()
        {
            ReusableEnumerators.AddUser<ConfigureRoadSystem>();
            ReusableEnumerators.AddUser<ConfigureRoadCells>();
        }

        private void OnDisable()
        {
            ReusableEnumerators.RemoveUser<ConfigureRoadSystem>();
            ReusableEnumerators.RemoveUser<ConfigureRoadCells>();
        }

        public override IEnumerator<YieldInstruction> OnBeforeCellsInBatchActivated(ReadOnlyList<WorldCell> cells, int batchNumber, int totalBatchesToExpect, bool cellsArePartOfLODTransition, bool immediateMode)
        {
            if (cells[0].WorldGroupingIndex == microverseGrouping)
                return ReusableEnumerators.GetEnumeratorNotInUse<ConfigureRoadSystem>().PrepareForIteration(this, cells);
            else
                return ReusableEnumerators.GetEnumeratorNotInUse<ConfigureRoadCells>().PrepareForIteration(this, cells);
        }

        class ConfigureRoadSystem : YieldEnumeratorWithParent_RefOnly<RoadAssetChunkManager, ReadOnlyList<WorldCell>>
        {
            int cellIndex;
            int childIndex, children;
            Transform roadSystemTransform;
            protected override void PerformAdditionalIterationPreparation()
            {
                cellIndex = 0;
                Current = YieldOrContinue.Instance;
            }
            protected override bool MoveNextImplementation()
            {
                switch(Phase)
                {
                    case 1:
                        {
                            var roadSystem = Parent.GetRoadSystem(r1[cellIndex]);
                            if(roadSystem != null)
                            {
                                Parent.cellRoadSystems.Add(r1[cellIndex].CellOnEndlessGrid, roadSystem);
                                roadSystem.generateAtLoad = false;
                                roadSystemTransform = roadSystem.transform;
                                children = roadSystemTransform.childCount;
                                if (children > 0)
                                {
                                    childIndex = children - 1;
                                    if (Parent.roadSystemChildrenToDestroyPerBatch == 0)
                                    {
                                        for (; childIndex > -1; childIndex--)
                                            Destroy(roadSystemTransform.GetChild(childIndex).gameObject);

                                        goto case 3;
                                    }
                                    else
                                    {
                                        Phase = 2;
                                        goto case 2;
                                    }
                                }
                            }

                            goto case 4;
                        }
                    case 2:
                        {
                            int endChildIndex = childIndex - Parent.roadSystemChildrenToDestroyPerBatch;
                            if (endChildIndex < -1)
                                endChildIndex = -1;

                            for (; childIndex > endChildIndex; childIndex--)
                                Destroy(roadSystemTransform.GetChild(childIndex).gameObject);

                            if (endChildIndex < 0)
                                goto case 3;
                            else
                                return true;
                        }
                    case 3:
                        {
                            roadSystemTransform = null;
                            cellIndex++;
                            if (cellIndex < r1.Count)
                            {
                                Phase = 1;
                                return true;
                            }
                            else
                                return false;
                        }
                    case 4://same as case 3 except there is no yield or continue check
                        {
                            cellIndex++;
                            if (cellIndex < r1.Count)
                            {
                                Phase = 1;
                                return MoveNextImplementation();
                            }
                            else
                                return false;
                        }
                    default:
                        throw new YieldEnumeratorException("ConfigureRoadSystem", $"MicroVerseRoadsConfigurer Component", Parent.gameObject.name, Phase);
                }
            }
        }

        RoadSystem GetRoadSystem(WorldCell worldCell)
        {
            GameObject microVerseRootChunk = (GameObject)worldCell.GetChunkBelongingToCell(1);
            //will exclude inactive game objects by default, which allows user to disable Road Systems on the 
            //root asset chunk if they want to temporarily stop using them
            var roadSystemComponents = microVerseRootChunk.GetComponentsInChildren<RoadSystem>();
            for (int i = 0; i < roadSystemComponents.Length; i++)
            {
                if (roadSystemComponents[i].name.Equals(roadSystemName))
                    return roadSystemComponents[i];
            }

            return null;
        }

        class ConfigureRoadCells : YieldEnumeratorWithParent_RefOnly<RoadAssetChunkManager, ReadOnlyList<WorldCell>>
        {
            int cellIndex, chunkIndex, colliderIndex, roadMaterialOverrideIndex;
            Material currentMaterialTemplate;
            protected override void PerformAdditionalIterationPreparation()
            {
                cellIndex = 0;
                chunkIndex = 1;
                Current = YieldOrContinue.Instance;
            }
            protected override bool MoveNextImplementation()
            {
                switch(Phase)
                {
                    case 1:
                        {
                            var cell = r1[cellIndex];
                            var chunk = (GameObject)cell.GetChunkBelongingToCell(chunkIndex);

                            if (Parent.enableColliders)
                            {
                                chunk.GetComponentsInChildren(Parent.colliders);
                                if (Parent.collidersToEnablePerBatch == 0)
                                {
                                    if (Parent.colliders.Count > 0)
                                    {
                                        foreach (var c in Parent.colliders)
                                            c.enabled = true;
                                        Parent.colliders.Clear();
                                    }
                                }
                                else if (Parent.colliders.Count > 0)
                                {
                                    colliderIndex = 0;
                                    Phase = 2;
                                    goto case 2;
                                }
                            }

                            goto case 3;
                        }
                    case 2://enabling colliders in batches
                        {
                            int endIndex = colliderIndex + Parent.collidersToEnablePerBatch;
                            if(endIndex > Parent.colliders.Count)
                                endIndex = Parent.colliders.Count;

                            for (; colliderIndex < endIndex; colliderIndex++)
                                Parent.colliders[colliderIndex].enabled = true;

                            if (endIndex >= Parent.colliders.Count)
                            {
                                Parent.colliders.Clear();
                                goto case 3;
                            }
                            else
                                return true;
                        }
                    case 3:
                        {
                            var cell = r1[cellIndex];
                            var chunk = (GameObject)cell.GetChunkBelongingToCell(chunkIndex);
                            //fix
                            var microVerseEndlessGridCell = cell.World.FindEndlessGridCellPositionIsInOrClosestTo(cell.CellPosition, 1, Parent.microverseGrouping, out bool positionIsInCell);
                            if (Parent.cellRoadSystems.TryGetValue(microVerseEndlessGridCell, out RoadSystem controllingRoadSystem))
                            {
                                if (controllingRoadSystem.templateMaterial != null)
                                {
                                    chunk.GetComponentsInChildren(Parent.roadMaterialOverrides);
                                    if(Parent.roadMaterialOverrides.Count > 0)
                                    {
                                        if(Parent.overridesToProcessPerBatch == 0)
                                        {
                                            for (int i = Parent.roadMaterialOverrides.Count - 1; i > -1; i--)
                                                ProcessRoadMaterialOverride(Parent.roadMaterialOverrides[i], controllingRoadSystem.templateMaterial);

                                            Parent.roadMaterialOverrides.Clear();
                                        }
                                        else
                                        {
                                            currentMaterialTemplate = controllingRoadSystem.templateMaterial;
                                            roadMaterialOverrideIndex = Parent.roadMaterialOverrides.Count - 1;
                                            Phase = 4;
                                            goto case 4;
                                        }
                                    }                                    
                                }
                            }
                            else
                                Debug.LogError($"Could not find the Controlling Road System for the Road Based Asset Chunk on Grouping {cell.WorldGroupingIndex}. The Controlling Road System should have been present on World Grouping {Parent.microverseGrouping}, on Endless Grid Cell {microVerseEndlessGridCell}. Perhaps the Road System Name you provided ({Parent.roadSystemName}) does not match the name of the Road System found on the Micro Verse root object?");

                            goto case 5;
                        }
                    case 4:
                        {
                            int endChildIndex = roadMaterialOverrideIndex - Parent.overridesToProcessPerBatch;
                            if (endChildIndex < -1)
                                endChildIndex = -1;

                            for (; roadMaterialOverrideIndex > endChildIndex; roadMaterialOverrideIndex--)
                                ProcessRoadMaterialOverride(Parent.roadMaterialOverrides[roadMaterialOverrideIndex], currentMaterialTemplate);

                            if (endChildIndex < 0)
                            {
                                currentMaterialTemplate = null;
                                Parent.roadMaterialOverrides.Clear();
                                goto case 5;
                            }
                            else
                                return true;
                        }
                    case 5:
                        {
                            var cell = r1[cellIndex];
                            chunkIndex++;
                            if (chunkIndex > cell.NumChunks)
                            {
                                cellIndex++;//go to next cell
                                if (cellIndex == r1.Count)//if no more cells, exit
                                    return false;
                                else
                                    chunkIndex = 1;//otherwise, reset chunk index
                            }

                            Phase = 1;
                            return true;
                        }
                    default:
                        throw new YieldEnumeratorException("ConfigureRoadCells", $"MicroVerseRoadsConfigurer Component", Parent.gameObject.name, Phase);
                }
            }

            void ProcessRoadMaterialOverride(RoadMaterialOverride roadMaterialOverride, Material materialTemplate)
            {
                if (roadMaterialOverride.meshRenderer == null)
                    roadMaterialOverride.meshRenderer = roadMaterialOverride.GetComponent<MeshRenderer>();

                roadMaterialOverride.Override(materialTemplate);

                Destroy(roadMaterialOverride);
            }
        }

        public override IEnumerator<YieldInstruction> OnAfterCellsInBatchDeactivated(ReadOnlyList<WorldCell> cells, int batchNumber, int totalBatchesToExpect, bool cellsArePartOfLODTransition)
        {
            if (cells[0].WorldGroupingIndex == microverseGrouping)
            {
                foreach (var cell in cells)
                    cellRoadSystems.Remove(cell.CellOnEndlessGrid);
            }

            return SimpleYieldBreakEquivalentEnumerator.Instance;
        }
    }
}