using System.Collections.Generic;
using UnityEngine;

public class GrassField : MonoBehaviour {
    public int size = 100;
    public int numChunks = 5;
    public Material grassMaterial;
    public GrassChunk chunkPrefab;

    private struct BladeData {
        public Vector3 rootPosition;
        public Vector3 tipOffset;
    }

    // ReSharper disable Unity.PerformanceAnalysis
    public void GenerateGrassField() {
        Debug.Log("Generating grass field... ");

        var blades = GenerateBlades();
        CreateChunks(blades);

        Debug.Log("Generating grass field done");
    }

    private BladeData[] GenerateBlades() {
        var blades = new BladeData[size * size * 100];

        for (int x = 0; x < size * 10; x++)
        for (int y = 0; y < size * 10; y++) {
            var i = x + y * size * 10;
            blades[i].rootPosition = new Vector3(x/10.0f, 0.0f, y/10.0f);
            blades[i].tipOffset = new Vector3(0.01f, 1.0f, 0.01f);
        }

        return blades;
    }

    private void CreateChunks(BladeData[] blades) {
        GameObject chunks;
        // If a child called "Chunks" exists, destroy it -> children are also destroyed
        var chunksTransform = gameObject.transform.Find("Chunks");
        if (chunksTransform != null) {
            // Destroy(obj) does not work in edit mode
            DestroyImmediate(chunksTransform.gameObject);
        }
        // create GameObject "Chunks" as a container for all GrassChunk objects
        chunks = new GameObject("Chunks");
        chunks.transform.SetParent(this.transform, false);

        var chunkSize = ((float)size) / numChunks;
        for (int x = 0; x < numChunks; x++)
        for (int y = 0; y < numChunks; y++) {
            var chunkPos = new Vector3(x, 0.0f, y) * chunkSize;
            // bounds in local space of grass field
            var bounds = new Bounds(
                new Vector3(0.5f * chunkSize, 0.5f, 0.5f * chunkSize),
                new Vector3(chunkSize, 1.01f, chunkSize)
            );

            List<Vector3> rootPositions = new();
            List<Vector3> tipOffsets = new();
            List<int> indices = new();
            foreach (var blade in blades) {
                var localRootPos = blade.rootPosition - chunkPos;
                if (bounds.Contains(localRootPos)) {
                    rootPositions.Add(localRootPos);
                    tipOffsets.Add(blade.tipOffset);
                    indices.Add(rootPositions.Count - 1);
                }
            }

            var mesh = new Mesh();
            // 16 bit (default) supports 65536 vertices, 32 bit supports 4 billion
            mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
            mesh.name = "grass_chunk[" + x + "," + y + "]";
            mesh.SetVertices(rootPositions);
            // do not calculate bounding box - will be set manually
            mesh.SetIndices(indices, MeshTopology.Points, 0, false);
            // Vector3 UVs are possible
            mesh.SetUVs(0, tipOffsets);

            var chunk = Instantiate(chunkPrefab, chunks.transform);
            chunk.name = "Chunk [" + x + ", " + y +"]";
            chunk.transform.localPosition = chunkPos;

            var meshFilter = chunk.GetComponent<MeshFilter>();
            meshFilter.sharedMesh = mesh;

            var meshRenderer = chunk.GetComponent<MeshRenderer>();
            meshRenderer.localBounds = bounds;
            meshRenderer.sharedMaterial = grassMaterial;
        }
    }
}
