using System.Collections.Generic;
using UnityEngine;

namespace InfiniteWorld.VoxelWorld
{
    public sealed partial class VoxelWorldGenerator
    {
        private readonly Dictionary<Vector2Int, WorldChunkPlacementPlan> chunkPlacementPlans = new Dictionary<Vector2Int, WorldChunkPlacementPlan>();
        private readonly object placementPlanLock = new object();
        private IReadOnlyList<WorldPrefabCollectionRuntime> placementCollections => settings.PlacementCollections;

        public WorldChunkPlacementPlan GetChunkPlacementPlan(Vector2Int chunkCoord)
        {
            return GetOrCreateChunkPlacementPlan(chunkCoord);
        }

        public int GetBaseHeightAtWorldCell(Vector2Int worldCell)
        {
            return SampleRock(worldCell) ? SampleHeight(worldCell) : 0;
        }

        public int GetFinalHeightAtWorldCell(Vector2Int worldCell)
        {
            return GetSampledFinalHeightAtWorldCell(worldCell);
        }

        public byte GetBiomeIndexAtWorldCell(Vector2Int worldCell)
        {
            return SampleBiomeIndex(worldCell);
        }

        private WorldChunkPlacementPlan GetOrCreateChunkPlacementPlan(Vector2Int chunkCoord)
        {
            lock (placementPlanLock)
            {
                if (chunkPlacementPlans.TryGetValue(chunkCoord, out WorldChunkPlacementPlan cachedPlan))
                {
                    return cachedPlan;
                }
            }

            WorldChunkPlacementPlan plan = WorldSpawnPlanner.PlanChunk(seed, chunkCoord, chunkSize, placementCollections, GetBaseHeightAtWorldCell);

            lock (placementPlanLock)
            {
                if (chunkPlacementPlans.TryGetValue(chunkCoord, out WorldChunkPlacementPlan cachedPlan))
                {
                    return cachedPlan;
                }

                chunkPlacementPlans[chunkCoord] = plan;
                return plan;
            }
        }

        private int GetSampledFinalHeightAtWorldCell(Vector2Int worldCell)
        {
            int baseHeight = GetBaseHeightAtWorldCell(worldCell);
            Vector2Int chunkCoord = new Vector2Int(
                Mathf.FloorToInt(worldCell.x / (float)chunkSize),
                Mathf.FloorToInt(worldCell.y / (float)chunkSize));
            return GetOrCreateChunkPlacementPlan(chunkCoord).GetFinalHeight(worldCell, baseHeight);
        }

        private void ApplyChunkPlacementHeights(Vector2Int chunkCoord, int[] heights)
        {
            WorldChunkPlacementPlan plan = GetOrCreateChunkPlacementPlan(chunkCoord);
            if (!plan.HasFlattening)
            {
                return;
            }

            Vector2Int chunkOrigin = new Vector2Int(chunkCoord.x * chunkSize, chunkCoord.y * chunkSize);
            for (int z = 0; z < chunkSize; z++)
            {
                for (int x = 0; x < chunkSize; x++)
                {
                    Vector2Int worldCell = new Vector2Int(chunkOrigin.x + x, chunkOrigin.y + z);
                    heights[z * chunkSize + x] = plan.GetFinalHeight(worldCell, heights[z * chunkSize + x]);
                }
            }
        }

        private void CleanupPlacementPlans()
        {
            lock (placementPlanLock)
            {
                chunkPlacementPlans.Clear();
            }
        }
    }
}
