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

    [AddComponentMenu("Deep Space Labs/SAM/3rd Party/MicroVerse/Bounds Based Road Culling System")]
    public class BoundsBasedRoadCullingSystem : WorldGroupingListener, IWorldUser
    {
        [SerializeField, FieldRename("Road System Name*", "Road System Name\n\nThe name of the game object that contains the MicroVerse RoadSystem that this Roads Manager should manage. Note that each Bounds Based Roads Manager only manages a single Road System. If you have multiple Zones, you will need to use multiple Road Systems (with different names) across the different Zones. If you only have a single Zone but are using multiple Road Systems, you will also need multiple BoundsBasedRoadCullingSystems. Just make sure to assign each one to the World Grouping that contains your MicroVerse root Asset Chunks.")]
        string roadSystemName;

        [SerializeField, FieldRename("Track Active Grid Player*", "Track Active Grid Player\n\nYou have two options for setting the player whose position will be tracked.\n\n1) If you enable this option, the Player assigned to the Active Grid set in the 'Active Grid With Player' field will be tracked.\n\n2) If you disable this option, the Transform set via in the 'Player Transform' field will be tracked..")]
        bool trackActiveGridPlayer = true;

        [SerializeField, FieldRename("Active Grid With Player*", "trackActiveGridPlayer", true, "Active Grid With Player\n\nThe Active Grid whose Player will be tracked and whose position will be used in the distance checks.")]
        ActiveGrid activeGridWithPlayer;

        [SerializeField, FieldRename("Player Transform*", "trackActiveGridPlayer", false, "Active Grid With Player\n\nThe Transform the Player that will be tracked and whose position will be used in the distance checks.")]
        Transform playerTransform;

        [SerializeField, FieldRename("Active Range*", "Active Range\n\nThe distance from the player, in meters, that a road or intersection needs to reach before being deactivated.\n\nNote that each road/intersection has a bounds calculated it for that is a composite of all of it's child meshes. The distance this Active Range refers to is the closest distance from the player to this bounds.")]
        float activeRange = 500f;

        [SerializeField, FieldRename("Frequency*", "Frequency\n\nThe frequency, in seconds, that the distance comparison checks are run in order to determine which roads/intersections should be activated/deactivated.")]
        float frequency = 1f;

        [SerializeField, FieldRename("Max State Changes Per Frame*", "Max State Changes Per Frame\n\nThe maximum number of roads/intersections that can be activated or deactivated in a single frame. Use the largest possible value that does not cause frame hitching/freezing.")]
        int maxStateChangesPerFrame = 10;

        bool checkInProgress = false, waitingOnOriginCellChange;
        Dictionary<Cell, CellRoadSystem> unactivatedCellRoadSystems = new Dictionary<Cell, CellRoadSystem>();
        Dictionary<Cell, CellRoadSystem> activatedCellRoadSystems = new Dictionary<Cell, CellRoadSystem>();
        float activeRangeSquared, timeElapsed;
        Func<Vector3> GetPlayerPosition;
        int syncedWorldRegistrationID, syncedGrouping_zeroBased;
        JobHandle currentCheckJob;
        List<Renderer> reusableRendererList = new List<Renderer>(20);
        List<Collider> reusableColliderList = new List<Collider>(20);
        List<RoadMaterialOverride> reusableMaterialOverrideList = new List<RoadMaterialOverride>(20);
        World syncedWorld;
        int zoneIndex;//index of the Zone the found Road System belongs to

        //there should not be any LOD Transitions so set to ignore
        public override bool IgnoreLODTransitions => true;

        /// <summary>
        /// Gets or sets the Active Range to use.
        /// </summary>
        public float ActiveRange
        {
            get { return activeRange; } 
            set 
            {
                activeRange = value;
                activeRangeSquared = value * value;
            }
        }

        public override ListenerYieldBehavior YieldBehaviorAfterMethodExecution => ListenerYieldBehavior.YieldOrContinue;

        public int UserID => syncedWorldRegistrationID;

        public bool UseFastOnOriginCellChangedMethod => false;

        private void Awake()
        {
            ActiveRange = activeRange;

            if (trackActiveGridPlayer)
            {
                if (activeGridWithPlayer == null)
                {
                    Debug.LogWarning($"Track Active Grid is enabled but no Active Grid was assigned to the MicroVerse Bounds Based Roads Manager on game object {name}.\n\nYou must call either TrackActiveGrid or TrackTransformPlayer at runtime in order for this system to work!");

                    GetPlayerPosition = () => Vector3.zero;
                }
                else
                    GetPlayerPosition = () => (Vector3)activeGridWithPlayer.Player.Position;
            }
            else
            {
                if (playerTransform == null)
                { 
                    Debug.LogWarning($"Track Active Grid is disabled but no Player Transform was assigned to the MicroVerse Bounds Based Roads Manager on game object {name}.\n\nYou must call either TrackActiveGrid or TrackTransformPlayer at runtime in order for this system to work!");

                    GetPlayerPosition = () => Vector3.zero;
                }
                else
                    GetPlayerPosition = () => playerTransform.position;
            }
        }

        /// <summary>
        /// Use to either change tracking to tracking an Active Grid Player, or change the Active Grid whose Player is tracked.
        /// </summary>
        /// <param name="activeGrid">The Active Grid whose Player will be tracked.</param>
        /// <exception cref="ArgumentException">Thrown if activeGrid is null.</exception>
        public void TrackActiveGridPlayer(ActiveGrid activeGrid)
        {
            if(activeGrid == null)
                throw new ArgumentException($"TrackActiveGridPlayer was called but the activeGrid passed in was null! This is not allowed.");

            trackActiveGridPlayer = true;
            activeGridWithPlayer = activeGrid;
            GetPlayerPosition = () => (Vector3)activeGridWithPlayer.Player.Position;
        }

        /// <summary>
        /// Use to either change tracking to tracking an Player Transform, or change the PlayerTransform that is tracked.
        /// </summary>
        /// <param name="playerTransform">The Transform of the player that will be tracked.</param>
        /// <exception cref="ArgumentException">Thrown if playerTransform is null.</exception>
        public void TrackPlayerTransform(Transform playerTransform)
        {
            if (playerTransform == null)
                throw new ArgumentException($"TrackPlayerTransform was called but the playerTransform passed in was null! This is not allowed.");

            trackActiveGridPlayer = false;
            this.playerTransform = playerTransform;
            GetPlayerPosition = () => playerTransform.position;
        }

        private void OnEnable()
        {
            ReusableEnumerators.AddUser<GetRoadSystemsForCellsEnumerator>();
            ReusableEnumerators.AddUser<ActivateRoadSystemsEnumerator>();
            ReusableEnumerators.AddUser<ActivateRoadSystemEnumerator>();
            ReusableEnumerators.AddUser<RemoveRoadSystemsEnumerator>();
            ReusableEnumerators.AddUser<ShiftBoundsEnumerator>(false);//origin cell changing might not be used, so don't auto create enumerator
        }

        private void OnDisable()
        {
            ReusableEnumerators.RemoveUser<GetRoadSystemsForCellsEnumerator>();
            ReusableEnumerators.RemoveUser<ActivateRoadSystemsEnumerator>();
            ReusableEnumerators.RemoveUser<ActivateRoadSystemEnumerator>();
            ReusableEnumerators.RemoveUser<RemoveRoadSystemsEnumerator>();
            ReusableEnumerators.RemoveUser<ShiftBoundsEnumerator>();
        }

        private void OnDestroy()
        {
            if (checkInProgress)
                currentCheckJob.Complete();

            if(unactivatedCellRoadSystems.Count > 0)
            {
                var en = unactivatedCellRoadSystems.GetEnumerator();
                while(en.MoveNext())
                {
                    en.Current.Value.Dispose();
                }

                unactivatedCellRoadSystems.Clear();
            }

            if (activatedCellRoadSystems.Count > 0)
            {
                var en = activatedCellRoadSystems.GetEnumerator();
                while (en.MoveNext())
                {
                    en.Current.Value.Dispose();
                }

                activatedCellRoadSystems.Clear();
            }
        }

        void ActivateRoadSystemsInSingleFrame()
        {
            var en = unactivatedCellRoadSystems.GetEnumerator();
            Vector3 playerPosition = GetPlayerPosition();

            while (en.MoveNext())
            {
                var cellRoadSystem = en.Current.Value;

                new CalculateActiveRoadSystemChildren()
                {
                    childrenBounds = cellRoadSystem.roadSystemChildrenBounds,
                    isChildActive = cellRoadSystem.activeStateOfChildren,
                    playerPosition = playerPosition,
                    activeRangeSquared = activeRangeSquared
                }.Run(cellRoadSystem.roadSystemChildrenBounds.Length);

                int i = 0;
                cellRoadSystem.SetActiveStateOfTransforms(cellRoadSystem.roadSystemChildrenBounds.Length, ref i, out _);
                //cellRoadSystem.roadSystem.ReGenerateRoads();
                //cellRoadSystem.EnableColliders(reusableColliderList);

                activatedCellRoadSystems.Add(en.Current.Key, cellRoadSystem);
            }

            unactivatedCellRoadSystems.Clear();
        }

        bool jobsCompleted = false;
        Dictionary<Cell, CellRoadSystem>.Enumerator en;
        int childIndex = 0;
        private void Update()
        {
            timeElapsed += Time.deltaTime;

            if (checkInProgress)
            {
CheckStart:
                if (!jobsCompleted)
                {
                    if (!currentCheckJob.IsCompleted)
                        return;

                    currentCheckJob.Complete();
                    jobsCompleted = true;
                    en = activatedCellRoadSystems.GetEnumerator();
                    goto CheckStart;
                }
                else
                {
                    if (childIndex == 0)
                    {
                        if (en.MoveNext())
                        {
                            var cellRoadSystem = en.Current.Value;
                            cellRoadSystem.SetActiveStateOfTransforms(maxStateChangesPerFrame, ref childIndex, out bool allTransformsSet);

                            if (allTransformsSet)
                                childIndex = 0;
                        }
                        else
                        {
                            checkInProgress = false;
                        }
                    }
                    else
                    {
                        var cellRoadSystem = en.Current.Value;
                        cellRoadSystem.SetActiveStateOfTransforms(maxStateChangesPerFrame, ref childIndex, out bool allTransformsSet);
                        if (allTransformsSet)
                            childIndex = 0;
                    }
                }
            }
            else if (waitingOnOriginCellChange)
                return;
            else if (timeElapsed > frequency)
            {
                timeElapsed = 0f;
                checkInProgress = true;
                jobsCompleted = false;

                NativeArray<JobHandle> jobs = new NativeArray<JobHandle>(activatedCellRoadSystems.Count, Allocator.Temp, NativeArrayOptions.UninitializedMemory);

                int index = 0;
                Vector3 playerPosition = GetPlayerPosition();
                var en = activatedCellRoadSystems.GetEnumerator();

                while (en.MoveNext())
                {
                    var cellRoadSystem = en.Current.Value;
                    jobs[index++] = new CalculateActiveRoadSystemChildren()
                    {
                        childrenBounds = cellRoadSystem.roadSystemChildrenBounds,
                        isChildActive = cellRoadSystem.activeStateOfChildren,
                        playerPosition = playerPosition,
                        activeRangeSquared = activeRangeSquared
                    }.Schedule(cellRoadSystem.roadSystemChildrenBounds.Length, 64);
                }
                currentCheckJob = JobHandle.CombineDependencies(jobs);
                JobHandle.ScheduleBatchedJobs();
                jobs.Dispose();
            }
        }

        //we want to intercept the cell before it has been activated, in order to configure the initial Roads/Intersections that should be 
        //activated/deactivated
        public override IEnumerator<YieldInstruction> OnBeforeCellsInBatchActivated(ReadOnlyList<WorldCell> cells, int batchNumber, int totalBatchesToExpect, bool cellsArePartOfLODTransition, bool immediateMode)
        {
            return ReusableEnumerators.GetEnumeratorNotInUse<GetRoadSystemsForCellsEnumerator>().PrepareForIteration(this, cells);
        }

        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))
                {
                    if(roadSystemSubPath == null)
                    {
                        string microverseRootChunkName = microVerseRootChunk.name;
                        Transform t = roadSystemComponents[i].transform.parent;
                        roadSystemSubPath = "";
                        while(!t.name.Equals(microverseRootChunkName))
                        {
                            roadSystemSubPath = $"{t.name}/" + roadSystemSubPath;
                            t = t.parent;
                        }
                    }
                    return roadSystemComponents[i];
                }
            }

            return null;
        }

        string roadSystemSubPath;
        Transform GetRoadSystemRoot(WorldCell worldCell)
        {
            GameObject microVerseRootChunk = (GameObject)worldCell.GetChunkBelongingToCell(1);
            Transform t = microVerseRootChunk.transform;
            if (roadSystemSubPath == null)
                return t.GetChild(0).Find(roadSystemName);
            else
                return t.Find(roadSystemSubPath + roadSystemName);
        }

        class GetRoadSystemsForCellsEnumerator : YieldEnumeratorWithParent_RefOnly<BoundsBasedRoadCullingSystem, ReadOnlyList<WorldCell>>
        {
            int cellIndex, childIndex;
            protected override void PerformAdditionalIterationPreparation()
            {
                Current = YieldOrContinue.Instance;
                cellIndex = childIndex = 0;
                if (Parent.syncedWorld == null)
                {
                    Parent.syncedWorld = r1[0].World;
                    Parent.syncedWorld.Register(Parent, out Parent.syncedWorldRegistrationID);
                    Parent.syncedGrouping_zeroBased = r1[0].WorldGroupingIndex - 1;
                }
            }
            protected override bool MoveNextImplementation()
            {
                switch(Phase)
                {
                    case 1:
                        {
                            CellRoadSystem cellRoadSystem = null;
                            var roadSystem = Parent.GetRoadSystem(r1[cellIndex]);
                            if (roadSystem != null)
                                cellRoadSystem = new CellRoadSystem(roadSystem);
                            else
                            {
                                //if the asset chunk is being reused, the RoadSystem component will not exist, however the roads/intersections 
                                //will have already been setup and can be used!
                                var roadSystemRoot = Parent.GetRoadSystemRoot(r1[cellIndex]);
                                if (roadSystemRoot != null)
                                    cellRoadSystem = new CellRoadSystem(roadSystemRoot);
                            }

                            if(cellRoadSystem != null)
                            { 
                                Parent.unactivatedCellRoadSystems.Add(r1[cellIndex].CellOnEndlessGrid, cellRoadSystem);

                                if (cellRoadSystem.roadSystemChildren.Count > 0)
                                {
                                    Phase = 2;
                                    goto case 2;
                                }
                                else
                                    goto case 3;
                            }
                            else
                            {
                                Debug.Log($"No road system found for cell {r1[cellIndex].CellOnEndlessGrid}");
                                goto case 4;
                            }
                        }
                    case 2:
                        {
                            var cellRoadSystem = Parent.unactivatedCellRoadSystems[r1[cellIndex].CellOnEndlessGrid];
                            int endChildIndex = childIndex + 5;
                            bool allChildrenProcessed = endChildIndex >= cellRoadSystem.roadSystemChildren.Count;
                            if (allChildrenProcessed)
                                endChildIndex = cellRoadSystem.roadSystemChildren.Count;

                            for(; childIndex < endChildIndex; childIndex++)
                                cellRoadSystem.ConfigureChild(childIndex, Parent.reusableRendererList, Parent.reusableColliderList, Parent.reusableMaterialOverrideList);

                            if (!allChildrenProcessed)
                                return true;
                            else
                                goto case 3;
                        }
                    case 3:
                        {
                            var cellRoadSystem = Parent.unactivatedCellRoadSystems[r1[cellIndex].CellOnEndlessGrid];
                            if (cellRoadSystem.roadSystem != null)
                            {
                                Destroy(cellRoadSystem.roadSystem);
                                cellRoadSystem.roadSystem = null;
                            }
                            childIndex = 0;
                            goto case 4;                            
                        }
                    case 4:
                        {
                            cellIndex++;
                            if (cellIndex < r1.Count)
                            {
                                Phase = 1;
                                return true;
                            }
                            else
                                return false;
                        }
                    default:
                        throw new YieldEnumeratorException("GetRoadSystemsForCellsEnumerator", $"MicroVerseBoundsBasedRoadsManager Component", Parent.gameObject.name, Phase);
                }
            }
        }

        public override IEnumerator<YieldInstruction> OnAfterAllCellsActivated(ReadOnlyList<WorldCell> cells, int batchNumber, int totalBatchesToExpect, bool cellsArePartOfLODTransition, bool immediateMode)
        {
            if (immediateMode)
            {
                if(batchNumber == totalBatchesToExpect)
                    ActivateRoadSystemsInSingleFrame();

                return SimpleYieldBreakEquivalentEnumerator.Instance;
            }
            else
            {
                return ReusableEnumerators.GetEnumeratorNotInUse<ActivateRoadSystemsEnumerator>().PrepareForIteration(this, cells);
            }
        }

        

        class ActivateRoadSystemsEnumerator : YieldEnumeratorWithParent_RefOnly<BoundsBasedRoadCullingSystem, ReadOnlyList<WorldCell>>
        {
            int cellIndex;
            IEnumerator<YieldInstruction> en;

            protected override void PerformAdditionalIterationPreparation()
            {
                cellIndex = 0;
            }

            protected override bool MoveNextImplementation()
            {
                Start:
                if(en != null)
                {
                    if(en.MoveNext())
                    {
                        Current = en.Current;
                        return true;
                    }
                    else
                    {
                        en = null;
                        cellIndex++;

                        if (cellIndex < r1.Count)
                            goto Start;
                        else
                            return false;
                    }
                }
                else
                {
                    en = ReusableEnumerators.GetEnumeratorNotInUse<ActivateRoadSystemEnumerator>().PrepareForIteration(Parent, r1[cellIndex].CellOnEndlessGrid);
                    goto Start;
                }
            }
        }

        class ActivateRoadSystemEnumerator : YieldEnumeratorWithParent_StructOnly<BoundsBasedRoadCullingSystem, Cell>
        {
            JobHandle job;
            int childIndex;

            protected override bool MoveNextImplementation()
            {
                switch(Phase)
                {
                    case 1:
                        {
                            if (!Parent.unactivatedCellRoadSystems.ContainsKey(s1))
                                return false;

                            var cellRoadSystem = Parent.unactivatedCellRoadSystems[s1];
                            Vector3 playerPosition = Parent.GetPlayerPosition();
                            job = new CalculateActiveRoadSystemChildren()
                            {
                                childrenBounds = cellRoadSystem.roadSystemChildrenBounds,
                                isChildActive = cellRoadSystem.activeStateOfChildren,
                                playerPosition = playerPosition,
                                activeRangeSquared = Parent.activeRangeSquared
                            }.Schedule(cellRoadSystem.roadSystemChildrenBounds.Length, 64);

                            JobHandle.ScheduleBatchedJobs();
                            Phase = 2;
                            return true;
                        }
                    case 2:
                        {
                            if (!job.IsCompleted)
                                return true;

                            job.Complete();
                            childIndex = 0;
                            var cellRoadSystem = Parent.unactivatedCellRoadSystems[s1];

                            cellRoadSystem.SetActiveStateOfTransforms(Parent.maxStateChangesPerFrame, ref childIndex, out bool allTransformsSet);
                            if (allTransformsSet)
                                goto case 4;
                            else
                            {
                                Phase = 3;
                                return true;
                            }
                        }
                    case 3:
                        {
                            var cellRoadSystem = Parent.unactivatedCellRoadSystems[s1];
                            cellRoadSystem.SetActiveStateOfTransforms(Parent.maxStateChangesPerFrame, ref childIndex, out bool allTransformsSet);
                            if (allTransformsSet)
                                goto case 4;
                            else
                                return true;
                        }
                    case 4:
                        {
                            if (Parent.checkInProgress)
                            {
                                Phase = 5;
                                return true;
                            }
                            else
                                goto case 6;
                        }
                    case 5:
                        {
                            if (Parent.checkInProgress)
                                return true;

                            goto case 6;
                        }
                    case 6://only perform this action when a check is not in progress
                        {
                            var cellRoadSystem = Parent.unactivatedCellRoadSystems[s1];
                            Parent.activatedCellRoadSystems.Add(s1, cellRoadSystem);
                            Parent.unactivatedCellRoadSystems.Remove(s1);
                            return false;
                        }
                    default:
                        throw new YieldEnumeratorException("ActivateRoadSystemEnumerator", $"MicroVerseBoundsBasedRoadsManager Component", Parent.gameObject.name, Phase);
                }
            }
        }

        public override IEnumerator<YieldInstruction> OnAfterAllCellsDeactivated(ReadOnlyList<WorldCell> cells, int batchNumber, int totalBatchesToExpect, bool cellsArePartOfLODTransition)
        {
            return ReusableEnumerators.GetEnumeratorNotInUse<RemoveRoadSystemsEnumerator>().PrepareForIteration(this, cells);
        }

        class RemoveRoadSystemsEnumerator : YieldEnumeratorWithParent_RefOnly<BoundsBasedRoadCullingSystem, ReadOnlyList<WorldCell>>
        {
            protected override bool MoveNextImplementation()
            {
                if (Parent.checkInProgress)
                    return true;

                foreach (var cell in r1)
                {
                    if (Parent.activatedCellRoadSystems.TryGetValue(cell.CellOnEndlessGrid, out var cellRoadSystem))
                    {
                        cellRoadSystem.Dispose();
                        Parent.activatedCellRoadSystems.Remove(cell.CellOnEndlessGrid);
                    }
                }

                if (Parent.activatedCellRoadSystems.Count == 0)
                {
                    Parent.syncedWorld.DeRegister(Parent.syncedWorldRegistrationID);
                    Parent.syncedWorld = null;
                    Parent.waitingOnOriginCellChange = false;//not sure if this is necessary
                }

                return false;
            }
        }

        public bool TryDesyncFromWorld(bool failureWillDelayDestruction)
        {
            waitingOnOriginCellChange = false;
            syncedWorld = null;
            return true;
        }

        public bool IsReadyForOriginCellChange()
        {
            return !checkInProgress;
        }

        public void OnOriginCellChanging()
        {
            waitingOnOriginCellChange = true;
        }

        public void OnOriginCellChanged_Fast(OriginCellChangeInfo originCellChangeInfo)
        {
            throw new NotImplementedException();
        }

        public IEnumerator<YieldInstruction> OnOriginCellChanged_Slow(OriginCellChangeInfo originCellChangeInfo)
        {
            return ReusableEnumerators.GetEnumeratorNotInUse<ShiftBoundsEnumerator>().PrepareForIteration(this, (Vector3)originCellChangeInfo.ShiftAmount);
        }

        class ShiftBoundsEnumerator : YieldEnumeratorWithParent_StructOnly<BoundsBasedRoadCullingSystem, Vector3>
        {
            Dictionary<Cell, CellRoadSystem>.Enumerator en;

            protected override void PerformAdditionalIterationPreparation()
            {
                en = Parent.activatedCellRoadSystems.GetEnumerator();
                Current = YieldOrContinue.Instance;
            }

            protected override bool MoveNextImplementation()
            {
                if (en.MoveNext())
                {
                    en.Current.Value.ShiftBounds(s1);
                    return true;
                }
                else
                {
                    Parent.waitingOnOriginCellChange = false;
                    return false;
                }
            }
        }

        //Fix(to-do)
        public void OnOriginCellsClamped(ClampingOperationInfo operationInfo)
        {
            Cell clampingShiftForGrouping = Cell.Zero;// operationInfo.[syncedGrouping_zeroBased];
            if(unactivatedCellRoadSystems.Count > 0)
            {
                var newDict = new Dictionary<Cell, CellRoadSystem>(unactivatedCellRoadSystems.Count);
                var en = unactivatedCellRoadSystems.GetEnumerator();
                while (en.MoveNext())
                    newDict.Add(en.Current.Key + clampingShiftForGrouping, en.Current.Value);

                unactivatedCellRoadSystems.Clear();
                unactivatedCellRoadSystems = newDict;
            }

            if (activatedCellRoadSystems.Count > 0)
            {
                var newDict = new Dictionary<Cell, CellRoadSystem>(activatedCellRoadSystems.Count);
                var en = activatedCellRoadSystems.GetEnumerator();
                while (en.MoveNext())
                    newDict.Add(en.Current.Key + clampingShiftForGrouping, en.Current.Value);

                activatedCellRoadSystems.Clear();
                activatedCellRoadSystems = newDict;
            }
        }

        class CellRoadSystem
        {
            public RoadSystem roadSystem;

            public NativeArray<Bounds> roadSystemChildrenBounds;
            public NativeArray<bool> activeStateOfChildren;
            public List<GameObject> roadSystemChildren;

            public CellRoadSystem(RoadSystem roadSystem)
            {
                this.roadSystem = roadSystem;
                roadSystem.generateAtLoad = false;
                roadSystem.hideGameObjects = true;

                var roadSystemTransform = roadSystem.transform;
                int children = roadSystemTransform.childCount;

                roadSystemChildren = new List<GameObject>(children);

                for (int i = children - 1; i > -1; i--)
                {
                    var child = roadSystemTransform.GetChild(i).gameObject;
                    if (!child.activeSelf)
                        Destroy(child);//remove inactive child objects to make things easier later on
                    else
                    {
                        child.SetActive(false);
                        roadSystemChildren.Add(child);
                    }
                }

                roadSystemChildrenBounds = new NativeArray<Bounds>(roadSystemChildren.Count, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
                activeStateOfChildren = new NativeArray<bool>(roadSystemChildren.Count, Allocator.Persistent, NativeArrayOptions.ClearMemory);
            }

            public CellRoadSystem(Transform roadSystemTransform)
            {
                int children = roadSystemTransform.childCount;

                roadSystemChildren = new List<GameObject>(children);

                for (int i = children - 1; i > -1; i--)
                {
                    //deactivated children would already be rooted out and disposed of, no need to check for them
                    var child = roadSystemTransform.GetChild(i).gameObject;
                    child.SetActive(false);
                    roadSystemChildren.Add(child);
                }

                roadSystemChildrenBounds = new NativeArray<Bounds>(roadSystemChildren.Count, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
                activeStateOfChildren = new NativeArray<bool>(roadSystemChildren.Count, Allocator.Persistent, NativeArrayOptions.ClearMemory);
            }

            public void Dispose()
            {
                roadSystemChildrenBounds.Dispose();
                activeStateOfChildren.Dispose();
            }

            public void SetActiveStateOfTransforms(int iterations, ref int index, out bool allTransformsSet)
            {
                int endIteration = index + iterations;
                if(endIteration >= roadSystemChildren.Count)
                {
                    endIteration = roadSystemChildren.Count;
                    allTransformsSet = true;
                }
                else
                {
                    allTransformsSet = false;
                }

                for (; index < endIteration; index++)
                    roadSystemChildren[index].SetActive(activeStateOfChildren[index]);
            }

            public void ConfigureChild(int childIndex, List<Renderer> reusableRendererList, List<Collider> reusableColliderList, List<RoadMaterialOverride> reusableMaterialOverrideList)
            {
                var child = roadSystemChildren[childIndex];

                if (roadSystem != null)
                {
                    //generate road/intersection sub-meshes
                    if (child.TryGetComponent(out Road road))
                    {
                        road.Generate(roadSystem, false);

#if !UNITY_EDITOR//destroying the road component while in editor will result in MicroVerse removing all the generated road objects
                    Destroy(road);
#endif
                    }
                    else if (child.TryGetComponent(out Intersection intersection))
                    {
                        intersection.Generate(roadSystem);

                        //destroying the intersection while in the editor does not present any issues
                        Destroy(intersection);
                    }

                    //enable colliders
                    child.GetComponentsInChildren(true, reusableColliderList);
                    if (reusableColliderList.Count > 0)
                    {
                        foreach (var c in reusableColliderList)
                            c.enabled = true;
                        reusableColliderList.Clear();
                    }

                    //set material overrides
                    if (roadSystem.templateMaterial != null)
                    {
                        child.GetComponentsInChildren(true, reusableMaterialOverrideList);
                        for (int i = reusableMaterialOverrideList.Count - 1; i > -1; i--)
                        {
                            var overrideMaterial = reusableMaterialOverrideList[i];
                            if (overrideMaterial.meshRenderer == null)
                                overrideMaterial.meshRenderer = overrideMaterial.GetComponent<MeshRenderer>();

                            overrideMaterial.Override(roadSystem.templateMaterial);

                            Destroy(overrideMaterial);
                        }
                        reusableMaterialOverrideList.Clear();
                    }
                }

                //configure child bounds
                child.GetComponentsInChildren(true, reusableRendererList);

                if (reusableRendererList.Count > 0)
                {
                    var bounds = reusableRendererList[0].bounds;
                    for (int j = 1; j < reusableRendererList.Count; j++)
                        bounds.Encapsulate(reusableRendererList[j].bounds);

                    roadSystemChildrenBounds[childIndex] = bounds;
                    reusableRendererList.Clear();
                }
                else
                {
                    roadSystemChildrenBounds[childIndex] = new Bounds(roadSystemChildren[childIndex].transform.position, Vector3.one);
                }
            }

            public void ShiftBounds(Vector3 shiftAmount)
            {
                for (int i = 0; i < roadSystemChildrenBounds.Length; i++)
                    roadSystemChildrenBounds[i] = new Bounds(roadSystemChildrenBounds[i].center + shiftAmount, roadSystemChildrenBounds[i].size);
            }
        }

        struct CalculateActiveRoadSystemChildren : IJobParallelFor
        {
            [ReadOnly]
            public NativeArray<Bounds> childrenBounds;

            [WriteOnly]
            public NativeArray<bool> isChildActive;

            public Vector3 playerPosition;
            public float activeRangeSquared;

            public void Execute(int index)
            {
                isChildActive[index] = childrenBounds[index].SqrDistance(playerPosition) < activeRangeSquared;
            }
        }
    }
}