﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace JumpTower
{
    /// <summary>
    /// Contains all data and routines for generating a cylinder mesh.
    /// </summary>
    public class CylinderData
    {
        // Number of sides (segments) that define the circular cross-section.
        public int numside { get; private set; }
        // Height (length) of the cylinder.
        public float length { get; private set; }
        // Radius of the cylinder.
        public float radius { get; private set; }

        // The polygon that represents the cylinder's circular cross-section in the XZ-plane.
        private Polygon polygon;
        // MeshData is a helper class (not shown here) used to collect vertices, UVs, normals, and triangle indices.
        private MeshData meshData;
        // Array of generated Unity Mesh objects.
        public Mesh[] mesh { get; private set; }

        /// <summary>
        /// Constructor: sets the cylinder parameters and immediately generates the geometry.
        /// </summary>
        /// <param name="numside">Number of sides (segments) for the cylinder.</param>
        /// <param name="length">Height of the cylinder.</param>
        /// <param name="radius">Radius of the cylinder.</param>
        public CylinderData(int numside, float length, float radius)
        {
            this.numside = numside;
            this.length = length;
            this.radius = radius;
            Generate();
        }

        /// <summary>
        /// Generates the geometry for the cylinder including the top cap, bottom cap, and side faces.
        /// </summary>
        public void Generate()
        {
            // Create a polygon in the XZ-plane with the specified number of sides and radius.
            polygon = new Polygon(numside, radius);

            // Initialize MeshData to store the geometry (vertices, normals, UVs, and triangles).
            meshData = new MeshData();

            // Process each segment of the polygon to create geometry.
            // Each iteration handles one edge (from vertex i to vertex i+1) of the polygon.
            for (int i = 0; i < polygon.vertices.Length; i++)
            {
                // Compute the next vertex index; use modulo to wrap around to the first vertex.
                int j = (i + 1) % polygon.vertices.Length;

                // Start a new submesh part for the current segment.
                meshData.AddPart();

                // Get the current and next vertices on the polygon (in XZ coordinates).
                Vector2 p_i = polygon.vertices[i];
                Vector2 p_j = polygon.vertices[j];

                // Define Y-levels for the top and bottom faces:
                // Top face is at y = 0 and bottom face at y = length.
                // (Swap these values if you want a different orientation.)

                //====================================================
                // 1) TOP CAP
                //====================================================
                // Map each vertex from the 3D position to a 2D UV coordinate.
                // This mapping centers and scales the polygon so the texture fits a circle.
                Vector2 uv_i_top = new Vector2((p_i.x / radius) * 0.5f + 0.5f,
                                               (p_i.y / radius) * 0.5f + 0.5f);
                Vector2 uv_j_top = new Vector2((p_j.x / radius) * 0.5f + 0.5f,
                                               (p_j.y / radius) * 0.5f + 0.5f);
                // The center of the top cap in UV space.
                Vector2 uv_center_top = new Vector2(0.5f, 0.5f);

                // Normal for the top face points upward.
                Vector3 normalTop = Vector3.up;

                // Add vertices for the top cap:
                // - The two outer vertices (p_i and p_j).
                // - A center vertex at (0,0,0) for the top cap.
                int topV_i = meshData.AddVertex(new Vector3(p_i.x, 0f, p_i.y), uv_i_top, normalTop) - 1;
                int topV_j = meshData.AddVertex(new Vector3(p_j.x, 0f, p_j.y), uv_j_top, normalTop) - 1;
                int topV_center = meshData.AddVertex(new Vector3(0f, 0f, 0f), uv_center_top, normalTop) - 1;

                // Create a triangle for the top cap. The winding order ensures the triangle faces upward.
                meshData.AddTriangle(topV_i, topV_j, topV_center);

                //====================================================
                // 2) BOTTOM CAP
                //====================================================
                // Map UV coordinates for the bottom cap similarly to the top.
                Vector2 uv_i_bot = new Vector2((p_i.x / radius) * 0.5f + 0.5f,
                                               (p_i.y / radius) * 0.5f + 0.5f);
                Vector2 uv_j_bot = new Vector2((p_j.x / radius) * 0.5f + 0.5f,
                                               (p_j.y / radius) * 0.5f + 0.5f);
                Vector2 uv_center_bot = new Vector2(0.5f, 0.5f);

                // Normal for the bottom face points downward.
                Vector3 normalBottom = Vector3.down;

                // Add vertices for the bottom cap:
                // - The two outer vertices (p_i and p_j) at y = length.
                // - A center vertex for the bottom cap.
                int botV_i = meshData.AddVertex(new Vector3(p_i.x, length, p_i.y), uv_i_bot, normalBottom) - 1;
                int botV_j = meshData.AddVertex(new Vector3(p_j.x, length, p_j.y), uv_j_bot, normalBottom) - 1;
                int botV_center = meshData.AddVertex(new Vector3(0f, length, 0f), uv_center_bot, normalBottom) - 1;

                // Create a triangle for the bottom cap.
                // The vertex order is reversed so that the triangle faces downward.
                meshData.AddTriangle(botV_j, botV_i, botV_center);

                //====================================================
                // 3) SIDE FACES
                //====================================================
                // For the side faces, set up UV mapping based on the angular position:
                // - u: the fractional angle (i/numSides) around the cylinder.
                // - v: 0 at the top edge and 1 at the bottom edge.
                float u_i = polygon.angularUvs[i];
                float u_j = polygon.angularUvs[j];

                Vector2 uv_i_side_top = new Vector2(u_i, 0f);
                Vector2 uv_j_side_top = new Vector2(u_j, 0f);
                Vector2 uv_i_side_bottom = new Vector2(u_i, 1f);
                Vector2 uv_j_side_bottom = new Vector2(u_j, 1f);

                // Compute outward normals for the side vertices.
                // These are calculated by taking the vertex positions in the XZ-plane and normalizing.
                Vector3 normal_i_side = new Vector3(p_i.x, 0f, p_i.y).normalized;
                Vector3 normal_j_side = new Vector3(p_j.x, 0f, p_j.y).normalized;

                // Create four vertices for the quad that forms this side segment:
                // Top and bottom vertices for the current segment edge (p_i and p_j).
                int sideTop_i = meshData.AddVertex(
                    new Vector3(p_i.x, 0f, p_i.y),
                    uv_i_side_top,
                    normal_i_side
                ) - 1;

                int sideTop_j = meshData.AddVertex(
                    new Vector3(p_j.x, 0f, p_j.y),
                    uv_j_side_top,
                    normal_j_side
                ) - 1;

                int sideBot_i = meshData.AddVertex(
                    new Vector3(p_i.x, length, p_i.y),
                    uv_i_side_bottom,
                    normal_i_side
                ) - 1;

                int sideBot_j = meshData.AddVertex(
                    new Vector3(p_j.x, length, p_j.y),
                    uv_j_side_bottom,
                    normal_j_side
                ) - 1;

                // Create two triangles to form the side quad.
                // Triangle 1: top left (p_i), top right (p_j), bottom left (p_i).
                meshData.AddTriangle(sideTop_i, sideTop_j, sideBot_i);
                // Triangle 2: top right (p_j), bottom right (p_j), bottom left (p_i).
                meshData.AddTriangle(sideTop_j, sideBot_j, sideBot_i);

                // The code below adds additional triangles.
                // Their purpose seems to be to add extra detail or fill gaps, but you can modify or remove them as needed.
                meshData.AddTriangle(sideTop_i, sideBot_i, topV_center);
                int sideRTop_i = meshData.AddVertex(
                    new Vector3(0f, 0f, 0f),
                    uv_i_side_top,
                    normalTop
                ) - 1;
                int sideRBot_i = meshData.AddVertex(
                    new Vector3(0f, length, 0f),
                    uv_i_side_bottom,
                    normalTop
                ) - 1;
                int topR_center = meshData.AddVertex(new Vector3(p_i.x, length, p_i.y), uv_center_top, normal_i_side) - 1;
                meshData.AddTriangle(sideRBot_i, topR_center, sideRTop_i);
                meshData.AddTriangle(sideTop_j, sideBot_j, topV_center);

                int sideLTop_j = meshData.AddVertex(
                    new Vector3(0f, 0f, 0f),
                    uv_j_side_top,
                    normalTop
                ) - 1;
                int sideLBot_j = meshData.AddVertex(
                    new Vector3(0f, length, 0f),
                    uv_j_side_bottom,
                    normalTop
                ) - 1;
                int topL_center = meshData.AddVertex(new Vector3(p_j.x, length, p_j.y), uv_center_top, normal_j_side) - 1;
                meshData.AddTriangle(sideLBot_j, topL_center, sideLTop_j);
                // The following triangle is commented out and may be an alternative for the side geometry.
                //meshData.AddTriangle(sideTop_j, sideBot_j, sideBot_i);
            }

            // Finally, convert the collected mesh data into actual Unity Mesh objects.
            mesh = meshData.CreatMesh();
        }
    }

    /// <summary>
    /// Holds the ring of points on the XZ-plane representing a regular polygon.
    /// These points define the circular cross-section of the cylinder.
    /// </summary>
    public class Polygon
    {
        // The vertices of the polygon in the XZ-plane.
        public Vector2[] vertices { get; private set; }
        // The fractional angular positions (as UV coordinates) for each vertex.
        public float[] angularUvs { get; private set; }
        // Number of sides (and vertices) in the polygon.
        private int numSides;

        /// <summary>
        /// Constructs a polygon with the specified number of sides and radius.
        /// </summary>
        /// <param name="numSides">Number of sides for the polygon.</param>
        /// <param name="radius">Radius of the polygon.</param>
        public Polygon(int numSides, float radius)
        {
            this.numSides = numSides;
            SetVertices(radius);
        }

        /// <summary>
        /// Calculates the vertices for the polygon evenly distributed around a circle.
        /// Also computes a UV mapping based on the angular position.
        /// </summary>
        /// <param name="radius">Radius of the circle in which the polygon is inscribed.</param>
        private void SetVertices(float radius)
        {
            vertices = new Vector2[numSides];
            angularUvs = new float[numSides];

            // The angle between adjacent vertices.
            float angleStep = 2.0f * Mathf.PI / numSides;

            for (int i = 0; i < numSides; i++)
            {
                float angle = angleStep * i; // Current angle in radians.
                float x = radius * Mathf.Cos(angle);  // X-coordinate in the XZ-plane.
                float z = radius * Mathf.Sin(angle);    // Z-coordinate in the XZ-plane.

                vertices[i] = new Vector2(x, z);
                // Store the relative angular position as a UV coordinate.
                angularUvs[i] = (float)i / numSides;
            }
        }
    }
}
