using System.Collections.Generic;
using UnityEngine;

namespace InfiniteWorld.VoxelWorld
{
    public sealed partial class VoxelWorldGenerator
    {
        private struct ChunkBuildResult
        {
            public ChunkBuildResult(Vector2Int coord, int[] heights, byte[] biomeIndices, int version, int session, int runtimeId)
            {
                Coord = coord;
                Heights = heights;
                BiomeIndices = biomeIndices;
                Version = version;
                Session = session;
                RuntimeId = runtimeId;
            }

            public Vector2Int Coord { get; }
            public int[] Heights { get; }
            public byte[] BiomeIndices { get; }
            public int Version { get; }
            public int Session { get; }
            public int RuntimeId { get; }
        }

        private struct ChunkMeshBuild
        {
            public ChunkMeshBuild(Mesh colliderMesh, ChunkRenderSnapshot renderSnapshot)
            {
                ColliderMesh = colliderMesh;
                RenderSnapshot = renderSnapshot;
            }

            public Mesh ColliderMesh { get; }
            public ChunkRenderSnapshot RenderSnapshot { get; }
        }

        private struct PendingColliderMeshApply
        {
            public PendingColliderMeshApply(Vector2Int coord, int version, int runtimeId, Mesh colliderMesh)
            {
                Coord = coord;
                Version = version;
                RuntimeId = runtimeId;
                ColliderMesh = colliderMesh;
            }

            public Vector2Int Coord { get; }
            public int Version { get; }
            public int RuntimeId { get; }
            public Mesh ColliderMesh { get; }
        }

        private sealed class ChunkRenderSnapshot
        {
            public ChunkRenderSnapshot(Vector3[] vertices, Vector3[] normals, Vector2[] uv0, Vector2[] uv1, int[] triangles)
            {
                Vertices = vertices;
                Normals = normals;
                Uv0 = uv0;
                Uv1 = uv1;
                Triangles = triangles;
            }

            public Vector3[] Vertices { get; }
            public Vector3[] Normals { get; }
            public Vector2[] Uv0 { get; }
            public Vector2[] Uv1 { get; }
            public int[] Triangles { get; }
            public bool IsEmpty => Vertices == null || Vertices.Length == 0 || Triangles == null || Triangles.Length == 0;
        }

        private struct RegionChunkSnapshot
        {
            public RegionChunkSnapshot(ChunkRenderSnapshot snapshot, Vector3 localOffset)
            {
                Snapshot = snapshot;
                LocalOffset = localOffset;
            }

            public ChunkRenderSnapshot Snapshot { get; }
            public Vector3 LocalOffset { get; }
        }

        private struct RegionBuildRequest
        {
            public RegionBuildRequest(Vector2Int regionCoord, int version, int session, RegionChunkSnapshot[] chunks)
            {
                RegionCoord = regionCoord;
                Version = version;
                Session = session;
                Chunks = chunks;
            }

            public Vector2Int RegionCoord { get; }
            public int Version { get; }
            public int Session { get; }
            public RegionChunkSnapshot[] Chunks { get; }
        }

        private struct RegionBuildResult
        {
            public RegionBuildResult(Vector2Int regionCoord, int version, int session, Vector3[] vertices, Vector3[] normals, Vector2[] uv0, Vector2[] uv1, int[] triangles, Bounds bounds)
            {
                RegionCoord = regionCoord;
                Version = version;
                Session = session;
                Vertices = vertices;
                Normals = normals;
                Uv0 = uv0;
                Uv1 = uv1;
                Triangles = triangles;
                Bounds = bounds;
            }

            public Vector2Int RegionCoord { get; }
            public int Version { get; }
            public int Session { get; }
            public Vector3[] Vertices { get; }
            public Vector3[] Normals { get; }
            public Vector2[] Uv0 { get; }
            public Vector2[] Uv1 { get; }
            public int[] Triangles { get; }
            public Bounds Bounds { get; }
            public bool IsEmpty => Vertices == null || Vertices.Length == 0 || Triangles == null || Triangles.Length == 0;

            public static RegionBuildResult CreateEmpty(Vector2Int regionCoord, int version, int session)
            {
                return new RegionBuildResult(regionCoord, version, session, null, null, null, null, null, new Bounds());
            }
        }

        private sealed class MeshBuffers
        {
            public readonly List<Vector3> Vertices = new List<Vector3>(512);
            public readonly List<Vector3> Normals = new List<Vector3>(512);
            public readonly List<Vector2> Uvs = new List<Vector2>(512);
            public readonly List<Vector2> TextureData = new List<Vector2>(512);
            public readonly List<int> Triangles = new List<int>(1024);

            public Mesh ToMesh(string meshName)
            {
                Mesh mesh = new Mesh { name = meshName };
                if (Vertices.Count == 0)
                {
                    return mesh;
                }

                mesh.indexFormat = Vertices.Count > 65535 ? UnityEngine.Rendering.IndexFormat.UInt32 : UnityEngine.Rendering.IndexFormat.UInt16;
                mesh.SetVertices(Vertices);
                if (Normals.Count == Vertices.Count)
                {
                    mesh.SetNormals(Normals);
                }
                if (Uvs.Count == Vertices.Count)
                {
                    mesh.SetUVs(0, Uvs);
                }
                if (TextureData.Count == Vertices.Count)
                {
                    mesh.SetUVs(1, TextureData);
                }
                mesh.SetTriangles(Triangles, 0, true);
                mesh.RecalculateBounds();
                return mesh;
            }

            public ChunkRenderSnapshot ToSnapshot()
            {
                return new ChunkRenderSnapshot(
                    Vertices.ToArray(),
                    Normals.ToArray(),
                    Uvs.ToArray(),
                    TextureData.ToArray(),
                    Triangles.ToArray());
            }
        }

        private sealed class ChunkRuntime
        {
            public Transform Root;
            public MeshCollider MountainCollider;
            public BoxCollider GroundCollider;
            public Mesh ColliderMesh;
            public ChunkRenderSnapshot RenderSnapshot;
            public int[] Heights;
            public byte[] BiomeIndices;
            public ChunkState State;
            public int Version;
            public int RuntimeId;

            public bool HasData => Heights != null && BiomeIndices != null;
            public bool IsRendered => State == ChunkState.Rendered && Root != null;

            public void EnsureCreated(Vector2Int coord, Transform parent, int chunkSize)
            {
                if (Root == null)
                {
                    GameObject chunkObject = new GameObject($"VoxelChunk_{coord.x}_{coord.y}");
                    chunkObject.transform.SetParent(parent, false);
                    Root = chunkObject.transform;
                    MountainCollider = chunkObject.AddComponent<MeshCollider>();
                    GroundCollider = chunkObject.AddComponent<BoxCollider>();
                }

                Root.localPosition = new Vector3(coord.x * chunkSize, 0f, coord.y * chunkSize);
                GroundCollider.size = new Vector3(chunkSize, 0.2f, chunkSize);
                GroundCollider.center = new Vector3(chunkSize * 0.5f, -0.1f, chunkSize * 0.5f);
            }

            public void ApplyRenderData(ChunkRenderSnapshot renderSnapshot)
            {
                RenderSnapshot = renderSnapshot;
            }

            public void ApplyColliderMesh(Mesh colliderMesh)
            {
                if (ColliderMesh != null)
                {
                    DestroyMesh(ColliderMesh);
                }

                ColliderMesh = colliderMesh;
                MountainCollider.sharedMesh = null;
                MountainCollider.sharedMesh = ColliderMesh != null && ColliderMesh.vertexCount > 0 ? ColliderMesh : null;
            }

            public void Dispose()
            {
                if (Root != null)
                {
                    if (Application.isPlaying)
                    {
                        Object.Destroy(Root.gameObject);
                    }
                    else
                    {
                        Object.DestroyImmediate(Root.gameObject);
                    }
                }

                DestroyMesh(ColliderMesh);
                RenderSnapshot = null;
            }

            private static void DestroyMesh(Mesh mesh)
            {
                if (mesh == null)
                {
                    return;
                }

                if (Application.isPlaying)
                {
                    Object.Destroy(mesh);
                }
                else
                {
                    Object.DestroyImmediate(mesh);
                }
            }
        }

        private sealed class RegionRuntime
        {
            public Transform Root;
            public MeshFilter Filter;
            public MeshRenderer Renderer;
            public Mesh RenderMesh;

            public void EnsureCreated(Vector2Int regionCoord, Transform parent, int chunkSize, int regionSizeInChunks, Material material)
            {
                if (Root == null)
                {
                    GameObject regionObject = new GameObject($"VoxelRegion_{regionCoord.x}_{regionCoord.y}");
                    regionObject.transform.SetParent(parent, false);
                    Root = regionObject.transform;
                    Filter = regionObject.AddComponent<MeshFilter>();
                    Renderer = regionObject.AddComponent<MeshRenderer>();
                }

                Root.localPosition = new Vector3(regionCoord.x * regionSizeInChunks * chunkSize, 0f, regionCoord.y * regionSizeInChunks * chunkSize);
                Renderer.sharedMaterial = material;
            }

            public void ApplyMesh(Mesh mesh)
            {
                DestroyMesh(RenderMesh);
                RenderMesh = mesh;
                Filter.sharedMesh = RenderMesh;
            }

            public void Dispose()
            {
                if (Root != null)
                {
                    if (Application.isPlaying)
                    {
                        Object.Destroy(Root.gameObject);
                    }
                    else
                    {
                        Object.DestroyImmediate(Root.gameObject);
                    }
                }

                DestroyMesh(RenderMesh);
            }

            private static void DestroyMesh(Mesh mesh)
            {
                if (mesh == null)
                {
                    return;
                }

                if (Application.isPlaying)
                {
                    Object.Destroy(mesh);
                }
                else
                {
                    Object.DestroyImmediate(mesh);
                }
            }
        }

        private enum ChunkState
        {
            None,
            Generating,
            SyncBuilding,
            ReadyToRender,
            Rendered
        }
    }
}
