using System.Collections.Generic;
using UnityEngine;

public class PlaneGenerator : MonoBehaviour
{
    public Material material;

    public float noiseLevel = 0.1f;
    public float noiseScale = 0.1f;

    private const int pqsResolution = 8;
    private const int sphereRadius = 2;

    private List<Quad> quads;

    // Start is called once before the first execution of Update after the MonoBehaviour is created
    void Start()
    {
        BuildRootQuads();
        UpdateMeshes();
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    private void BuildRootQuads()
    {
        var quadY = new Quad();
        var quadY1 = new Quad();
        var quadZ = new Quad();
        var quadZ1 = new Quad();
        var quadX = new Quad();
        var quadX1 = new Quad();

        quadY = BuildRootQuad(
            id: 1,
            eulerAngles: new Vector3(0, 0, 0),
            upperQuad: null, // quadX1,
            rightQuad: null, // quadZ,
            lowerQuad: null, // quadX,
            leftQuad: null); // quadZ1);

        quadZ = BuildRootQuad(
            id: 3,
            eulerAngles: new Vector3(-90, 0, 0),
            upperQuad: null, // quadX,
            rightQuad: null,
            lowerQuad: null,
            leftQuad: quadY);

        quadZ1 = BuildRootQuad(
            id: 4,
            eulerAngles: new Vector3(90, 0, 0),
            upperQuad: null, // quadX,
            rightQuad: quadY,
            lowerQuad: null,
            leftQuad: null);

        quadX = BuildRootQuad(
            id: 5,
            eulerAngles: new Vector3(0, 0, 90),
            upperQuad: quadY,
            rightQuad: null, // quadZ
            lowerQuad: null,
            leftQuad: null); //quadZ1

        quadX1 = BuildRootQuad(
            id: 6,
            eulerAngles: new Vector3(0, 0, -90),
            upperQuad: null,
            rightQuad: null,
            lowerQuad: quadY,
            leftQuad: null);

        ////quadY1 = BuildRootQuad(2, new Vector3(180, 0, 0));
        ////quadZ = BuildRootQuad(3, new Vector3(-90, 0, 0));
        ////quadZ1 = BuildRootQuad(4, new Vector3(90, 0, 0));
        ////quadX = BuildRootQuad(5, new Vector3(0, 0, -90));
        ////quadX1 = BuildRootQuad(6, new Vector3(0, 0, 90));

        quads = new List<Quad> { quadY, quadY1, quadZ, quadZ1, quadX, quadX1 };
    }

    private Quad BuildRootQuad(
        int id,
        Vector3 eulerAngles,
        Quad? upperQuad,
        Quad? rightQuad,
        Quad? lowerQuad,
        Quad? leftQuad)
    {
        var (vertices, uv) = CreateVertices();
        Rotate(vertices, eulerAngles);

        AddHeights(
            vertices,
            upperQuad,
            rightQuad,
            lowerQuad,
            leftQuad);

        var triangles = CreateTriangles();

        return new Quad
        {
            Id = id,
            Vertices = vertices,
            Triangles = triangles,
            Uv = uv
        };
    }

    private void UpdateMeshes()
    {
        foreach (var quad in quads)
        {
            CreateMesh($"quad_{quad.Id}", quad.Vertices, quad.Uv, quad.Triangles);
        }
    }

    private (Vector3[] vertices, Vector2[] uv) CreateVertices()
    {
        var half = sphereRadius / 2f;
        var vertices = new Vector3[(pqsResolution + 1) * (pqsResolution + 1)];
        Vector2[] uv = new Vector2[(pqsResolution + 1) * (pqsResolution + 1)];

        for (int x = 0, vertex = 0; x <= pqsResolution; x++)
        {
            for (int z = 0; z <= pqsResolution; z++, vertex++)
            {
                vertices[vertex] = new Vector3((float)x / pqsResolution * sphereRadius - half, half, (float)z / pqsResolution * sphereRadius - half);
                uv[vertex] = new Vector2((float)x / pqsResolution, (float)z / pqsResolution);
            }
        }

        return (vertices, uv);

    }

    private int[] CreateTriangles()
    {
        var triangles = new int[(pqsResolution) * (pqsResolution) * 6];

        int triangle = 0;
        for (int x = 1, vertex = 0; x<pqsResolution + 1; x++, vertex++)
        {
            for (int z = 1; z<pqsResolution + 1; z++, vertex++)
            {
                triangles[triangle] = vertex;
                triangles[triangle + 1] = vertex + pqsResolution + 2;
                triangles[triangle + 2] = vertex + pqsResolution + 1;

                triangles[triangle + 3] = vertex;
                triangles[triangle + 4] = vertex + 1;
                triangles[triangle + 5] = vertex + pqsResolution + 2;

                triangle += 6;
            }
        }

        return triangles;
    }

    private void CreateMesh(string name, Vector3[] vertices, Vector2[] uv, int[] triangles)
    {
        var mesh = new GameObject(name, typeof(MeshFilter), typeof(MeshRenderer), typeof(MeshCollider));
        mesh.transform.localScale = new Vector3(1, 1, 1);

        var meshFilter = mesh.GetComponent<MeshFilter>();
        meshFilter.mesh.vertices = vertices;
        meshFilter.mesh.triangles = triangles;
        meshFilter.mesh.uv = uv;

        mesh.GetComponent<MeshCollider>().sharedMesh = meshFilter.mesh;
        meshFilter.mesh.RecalculateBounds();
        meshFilter.mesh.RecalculateNormals();

        mesh.GetComponent<MeshRenderer>().material = material;
    }

    private void Rotate(Vector3[] vertices, Vector3 eulerAngles)
    {
        for (int x = 0, vertex = 0; x <= pqsResolution; x++)
        {
            for (int z = 0; z <= pqsResolution; z++, vertex++)
            {
                vertices[vertex] = Rotate(vertices[vertex], eulerAngles);
                vertices[vertex] = Vector3.Normalize(vertices[vertex]);
            }
        }
    }

    private Vector3 Rotate(Vector3 vertex, Vector3 eulerAngles)
    {
        var result = vertex;

        if (eulerAngles.x != 0)
            result = RotateX(result, eulerAngles.x);

        if (eulerAngles.y != 0)
            result = RotateY(result, eulerAngles.y);

        if (eulerAngles.z != 0)
            result = RotateZ(result, eulerAngles.z);

        return result;
    }

    private Vector3 RotateX(Vector3 vertex, float angle)
    {
        var angleDeg = Mathf.Deg2Rad * angle;

        return new Vector3(
            x: vertex.x,
            y: vertex.y * Mathf.Cos(angleDeg) - vertex.z * Mathf.Sin(angleDeg),
            z: vertex.y * Mathf.Sin(angleDeg) + vertex.z * Mathf.Cos(angleDeg));
    }

    private Vector3 RotateY(Vector3 vertex, float angle)
    {
        var angleDeg = Mathf.Deg2Rad * angle;

        return new Vector3(
            x: vertex.x * Mathf.Cos(angleDeg) + vertex.z * Mathf.Sin(angleDeg),
            y: vertex.y,
            z: -vertex.x * Mathf.Sin(angleDeg) + vertex.z * Mathf.Cos(angleDeg));
    }

    private Vector3 RotateZ(Vector3 vertex, float angle)
    {
        var angleDeg = Mathf.Deg2Rad * angle;

        return new Vector3(
            x: vertex.x * Mathf.Cos(angleDeg) - vertex.y * Mathf.Sin(angleDeg),
            y: vertex.x * Mathf.Sin(angleDeg) + vertex.y * Mathf.Cos(angleDeg),
            z: vertex.z);
    }

    private float GetNoise(float x, float y)
    {
        return Mathf.PerlinNoise(x * noiseScale, y * noiseScale) * noiseLevel;
    }

    private void AddHeights(
        Vector3[] vertices,
        Quad? upperQuad,
        Quad? rightQuad,
        Quad? lowerQuad,
        Quad? leftQuad)
    {
        for (int x = 0, vertex = 0; x <= pqsResolution; x++)
        {
            for (int z = 0; z <= pqsResolution; z++, vertex++)
            {
                if (x == 0 && lowerQuad?.Vertices != null)
                {
                    vertices[vertex] = lowerQuad.Value.Vertices[(pqsResolution + 1) * pqsResolution + z]; //+
                }
                else if (x == pqsResolution && upperQuad?.Vertices != null)
                {
                    vertices[vertex] = upperQuad.Value.Vertices[z];
                }
                else if (z == 0 && rightQuad?.Vertices != null)
                {
                    vertices[vertex] = rightQuad.Value.Vertices[(pqsResolution + 1) * (x + 1) - 1]; //+
                }
                else if (z == pqsResolution && leftQuad?.Vertices != null)
                {
                    vertices[vertex] = leftQuad.Value.Vertices[x * (pqsResolution + 1)]; //+
                }
                else
                {
                    var noise = 1 + GetNoise(Random.value, Random.value);
                    vertices[vertex] = noise * sphereRadius * vertices[vertex];
                }
            }
        }
    }

    private void OnDrawGizmos()
    {
        var gizmoSize = 0.05f;
        if (quads != null)
        {
            foreach (var quad in quads)
            {
                if (quad.Vertices == null)
                    continue;

                for (int x = 0, vertex = 0; x <= pqsResolution; x++)
                {
                    for (int z = 0; z <= pqsResolution; z++, vertex++)
                    {
                        if (x == 0)
                        {
                            Gizmos.DrawSphere(quad.Vertices[vertex], gizmoSize);
                        }
                        else if (x == pqsResolution)
                        {
                            Gizmos.DrawWireSphere(quad.Vertices[vertex], gizmoSize);
                        }
                        else if (z == 0)
                        {
                            Gizmos.DrawCube(quad.Vertices[vertex], new Vector3(gizmoSize, gizmoSize, gizmoSize));
                        }
                        else if (z == pqsResolution)
                        {
                            Gizmos.DrawWireCube(quad.Vertices[vertex], new Vector3(gizmoSize, gizmoSize, gizmoSize));
                        }
                        else
                        {
                            var noise = 1; // 1 + GetNoise(Random.value, Random.value);
                                           //vertices[vertex] = noise * sphereRadius * vertices[vertex];
                        }
                    }
                }
            }
        }
    }
}
