using System.Collections.Generic;
using UnityEngine;

namespace InfiniteWorld.VoxelWorld
{
    [CreateAssetMenu(menuName = "Infinite World/Voxel World Config", fileName = "VoxelWorldConfig")]
    public sealed class VoxelWorldConfig : ScriptableObject
    {
        [Header("Streaming")]
        [Min(8)] public int chunkSize = 16;
        [Min(1)] public int generationRadius = 3;
        [Min(0)] public int blockingGenerationRadius;
        public int seed = 12345;
        [Min(1)] public int maxMountainHeight = 3;

        [Header("Shape Noise")]
        public float macroNoiseScale = 0.05f;
        public float detailNoiseScale = 0.12f;
        public float ridgeNoiseScale = 0.18f;
        public float wallThreshold = 0.6f;
        public float rockBias = 0.04f;
        [Min(0)] public int smoothingPasses = 2;

        [Header("Global Passes")]
        public float passNoiseScale = 0.018f;
        public float passDetailScale = 0.041f;
        public float passThreshold = 0.22f;
        public float passFeather = 0.12f;

        [Header("Height")]
        public float heightNoiseScale = 0.08f;
        public float terraceNoiseScale = 0.17f;
        public float heightBias = 0.05f;

        [Header("Biomes")]
        public List<VoxelBiomeProfile> biomeProfiles = new List<VoxelBiomeProfile>();
        public float biomeNoiseScale = 0.02f;
        [Min(1f)] public float biomeSize = 48f;

        [Header("Placements")]
        public List<WorldPrefabCollection> placementCollections = new List<WorldPrefabCollection>();

        [Header("Runtime")]
        [Min(1)] public int maxAsyncChunkJobs = 2;
        [Min(1)] public int maxChunkBuildsPerFrame = 1;
        [Min(1)] public int maxChunkMeshBuildsPerFrame = 1;
        [Min(1)] public int maxColliderAppliesPerFrame = 1;
        [Min(0)] public int maxNeighborRefreshesPerFrame = 2;
        [Min(1)] public int renderRegionSizeInChunks = 4;
        [Min(1)] public int maxRegionBuildsPerFrame = 1;
    }

    internal readonly struct VoxelWorldResolvedSettings
    {
        private static readonly IReadOnlyList<VoxelBiomeProfile> EmptyBiomes = System.Array.Empty<VoxelBiomeProfile>();
        private static readonly IReadOnlyList<WorldPrefabCollectionRuntime> EmptyPlacementCollections = System.Array.Empty<WorldPrefabCollectionRuntime>();

        public static readonly VoxelWorldResolvedSettings Default = Resolve(null);

        public VoxelWorldResolvedSettings(
            int chunkSize,
            int generationRadius,
            int blockingGenerationRadius,
            int seed,
            int maxMountainHeight,
            float macroNoiseScale,
            float detailNoiseScale,
            float ridgeNoiseScale,
            float wallThreshold,
            float rockBias,
            int smoothingPasses,
            float passNoiseScale,
            float passDetailScale,
            float passThreshold,
            float passFeather,
            float heightNoiseScale,
            float terraceNoiseScale,
            float heightBias,
            IReadOnlyList<VoxelBiomeProfile> biomeProfiles,
            float biomeNoiseScale,
            float biomeSize,
            IReadOnlyList<WorldPrefabCollectionRuntime> placementCollections,
            int maxAsyncChunkJobs,
            int maxChunkBuildsPerFrame,
            int maxChunkMeshBuildsPerFrame,
            int maxColliderAppliesPerFrame,
            int maxNeighborRefreshesPerFrame,
            int renderRegionSizeInChunks,
            int maxRegionBuildsPerFrame)
        {
            ChunkSize = chunkSize;
            GenerationRadius = generationRadius;
            BlockingGenerationRadius = blockingGenerationRadius;
            Seed = seed;
            MaxMountainHeight = maxMountainHeight;
            MacroNoiseScale = macroNoiseScale;
            DetailNoiseScale = detailNoiseScale;
            RidgeNoiseScale = ridgeNoiseScale;
            WallThreshold = wallThreshold;
            RockBias = rockBias;
            SmoothingPasses = smoothingPasses;
            PassNoiseScale = passNoiseScale;
            PassDetailScale = passDetailScale;
            PassThreshold = passThreshold;
            PassFeather = passFeather;
            HeightNoiseScale = heightNoiseScale;
            TerraceNoiseScale = terraceNoiseScale;
            HeightBias = heightBias;
            BiomeProfiles = biomeProfiles;
            BiomeNoiseScale = biomeNoiseScale;
            BiomeSize = biomeSize;
            PlacementCollections = placementCollections;
            MaxAsyncChunkJobs = maxAsyncChunkJobs;
            MaxChunkBuildsPerFrame = maxChunkBuildsPerFrame;
            MaxChunkMeshBuildsPerFrame = maxChunkMeshBuildsPerFrame;
            MaxColliderAppliesPerFrame = maxColliderAppliesPerFrame;
            MaxNeighborRefreshesPerFrame = maxNeighborRefreshesPerFrame;
            RenderRegionSizeInChunks = renderRegionSizeInChunks;
            MaxRegionBuildsPerFrame = maxRegionBuildsPerFrame;
        }

        public int ChunkSize { get; }
        public int GenerationRadius { get; }
        public int BlockingGenerationRadius { get; }
        public int Seed { get; }
        public int MaxMountainHeight { get; }
        public float MacroNoiseScale { get; }
        public float DetailNoiseScale { get; }
        public float RidgeNoiseScale { get; }
        public float WallThreshold { get; }
        public float RockBias { get; }
        public int SmoothingPasses { get; }
        public float PassNoiseScale { get; }
        public float PassDetailScale { get; }
        public float PassThreshold { get; }
        public float PassFeather { get; }
        public float HeightNoiseScale { get; }
        public float TerraceNoiseScale { get; }
        public float HeightBias { get; }
        public IReadOnlyList<VoxelBiomeProfile> BiomeProfiles { get; }
        public float BiomeNoiseScale { get; }
        public float BiomeSize { get; }
        public IReadOnlyList<WorldPrefabCollectionRuntime> PlacementCollections { get; }
        public int MaxAsyncChunkJobs { get; }
        public int MaxChunkBuildsPerFrame { get; }
        public int MaxChunkMeshBuildsPerFrame { get; }
        public int MaxColliderAppliesPerFrame { get; }
        public int MaxNeighborRefreshesPerFrame { get; }
        public int RenderRegionSizeInChunks { get; }
        public int MaxRegionBuildsPerFrame { get; }

        public static VoxelWorldResolvedSettings Resolve(VoxelWorldConfig config)
        {
            IReadOnlyList<VoxelBiomeProfile> biomes = config != null && config.biomeProfiles != null
                ? config.biomeProfiles
                : EmptyBiomes;
            IReadOnlyList<WorldPrefabCollectionRuntime> placements = ResolvePlacementCollections(config);

            return new VoxelWorldResolvedSettings(
                Mathf.Max(8, config != null ? config.chunkSize : 16),
                Mathf.Max(1, config != null ? config.generationRadius : 3),
                Mathf.Max(0, config != null ? config.blockingGenerationRadius : 0),
                config != null ? config.seed : 12345,
                Mathf.Max(1, config != null ? config.maxMountainHeight : 3),
                config != null ? config.macroNoiseScale : 0.05f,
                config != null ? config.detailNoiseScale : 0.12f,
                config != null ? config.ridgeNoiseScale : 0.18f,
                config != null ? config.wallThreshold : 0.6f,
                config != null ? config.rockBias : 0.04f,
                Mathf.Max(0, config != null ? config.smoothingPasses : 2),
                config != null ? config.passNoiseScale : 0.018f,
                config != null ? config.passDetailScale : 0.041f,
                config != null ? config.passThreshold : 0.22f,
                config != null ? config.passFeather : 0.12f,
                config != null ? config.heightNoiseScale : 0.08f,
                config != null ? config.terraceNoiseScale : 0.17f,
                config != null ? config.heightBias : 0.05f,
                biomes,
                config != null ? config.biomeNoiseScale : 0.02f,
                Mathf.Max(1f, config != null ? config.biomeSize : 48f),
                placements,
                Mathf.Max(1, config != null ? config.maxAsyncChunkJobs : 2),
                Mathf.Max(1, config != null ? config.maxChunkBuildsPerFrame : 1),
                Mathf.Max(1, config != null ? config.maxChunkMeshBuildsPerFrame : 1),
                Mathf.Max(1, config != null ? config.maxColliderAppliesPerFrame : 1),
                Mathf.Max(0, config != null ? config.maxNeighborRefreshesPerFrame : 2),
                Mathf.Max(1, config != null ? config.renderRegionSizeInChunks : 4),
                Mathf.Max(1, config != null ? config.maxRegionBuildsPerFrame : 1));
        }

        private static IReadOnlyList<WorldPrefabCollectionRuntime> ResolvePlacementCollections(VoxelWorldConfig config)
        {
            if (config == null || config.placementCollections == null || config.placementCollections.Count == 0)
            {
                return EmptyPlacementCollections;
            }

            List<WorldPrefabCollectionRuntime> result = new List<WorldPrefabCollectionRuntime>(config.placementCollections.Count);
            for (int collectionIndex = 0; collectionIndex < config.placementCollections.Count; collectionIndex++)
            {
                WorldPrefabCollection collection = config.placementCollections[collectionIndex];
                if (WorldPlacementValidation.TryGetCollectionBlockerReason(collection, out _))
                {
                    continue;
                }

                List<WorldPrefabEntryRuntime> entries = new List<WorldPrefabEntryRuntime>();
                HashSet<string> usedIds = new HashSet<string>();
                if (collection.entries != null)
                {
                    for (int entryIndex = 0; entryIndex < collection.entries.Count; entryIndex++)
                    {
                        WorldPrefabEntry entry = collection.entries[entryIndex];
                        if (WorldPlacementValidation.TryGetEntryBlockerReason(entry, usedIds, out _))
                        {
                            continue;
                        }

                        entries.Add(new WorldPrefabEntryRuntime(
                            entryIndex,
                            entry.id,
                            entry.weight,
                            entry.spawnChancePercent,
                            entry.placementMode,
                            entry.footprint,
                            entry.clearance,
                            entry.flattenPadding,
                            entry.flattenSearchRadius,
                            entry.allowRotations,
                            entry.prefab != null));
                    }
                }

                result.Add(new WorldPrefabCollectionRuntime(
                    collectionIndex,
                    Mathf.Max(0, collection.maxPlacementsPerChunk),
                    Mathf.Max(1, collection.attemptsPerPlacement),
                    Mathf.Max(0, collection.chunkEdgePadding),
                    entries));
            }

            return result.Count > 0 ? result : EmptyPlacementCollections;
        }
    }
}
