using System;
using DotRecast.Core;
using DotRecast.Recast.Demo.Draw;
using static DotRecast.Core.RecastMath;

namespace DotRecast.Recast.Demo.Tools.Gizmos;

public class GizmoHelper
{
    private static readonly int SEGMENTS = 16;
    private static readonly int RINGS = 8;

    private static float[] sphericalVertices;

    public static float[] generateSphericalVertices()
    {
        if (sphericalVertices == null)
        {
            sphericalVertices = generateSphericalVertices(SEGMENTS, RINGS);
        }

        return sphericalVertices;
    }

    private static float[] generateSphericalVertices(int segments, int rings)
    {
        float[] vertices = new float[6 + 3 * (segments + 1) * (rings + 1)];
        // top
        int vi = 0;
        vertices[vi++] = 0;
        vertices[vi++] = 1;
        vertices[vi++] = 0;
        for (int r = 0; r <= rings; r++)
        {
            double theta = Math.PI * (r + 1) / (rings + 2);
            vi = generateRingVertices(segments, vertices, vi, theta);
        }

        // bottom
        vertices[vi++] = 0;
        vertices[vi++] = -1;
        vertices[vi++] = 0;
        return vertices;
    }

    public static float[] generateCylindricalVertices()
    {
        return generateCylindricalVertices(SEGMENTS);
    }

    private static float[] generateCylindricalVertices(int segments)
    {
        float[] vertices = new float[3 * (segments + 1) * 4];
        int vi = 0;
        for (int r = 0; r < 4; r++)
        {
            vi = generateRingVertices(segments, vertices, vi, Math.PI * 0.5);
        }

        return vertices;
    }

    private static int generateRingVertices(int segments, float[] vertices, int vi, double theta)
    {
        double cosTheta = Math.Cos(theta);
        double sinTheta = Math.Sin(theta);
        for (int p = 0; p <= segments; p++)
        {
            double phi = 2 * Math.PI * p / segments;
            double cosPhi = Math.Cos(phi);
            double sinPhi = Math.Sin(phi);
            vertices[vi++] = (float)(sinTheta * cosPhi);
            vertices[vi++] = (float)cosTheta;
            vertices[vi++] = (float)(sinTheta * sinPhi);
        }

        return vi;
    }

    public static int[] generateSphericalTriangles()
    {
        return generateSphericalTriangles(SEGMENTS, RINGS);
    }

    private static int[] generateSphericalTriangles(int segments, int rings)
    {
        int[] triangles = new int[6 * (segments + rings * (segments + 1))];
        int ti = generateSphereUpperCapTriangles(segments, triangles, 0);
        ti = generateRingTriangles(segments, rings, triangles, 1, ti);
        generateSphereLowerCapTriangles(segments, rings, triangles, ti);
        return triangles;
    }

    public static int generateRingTriangles(int segments, int rings, int[] triangles, int vertexOffset, int ti)
    {
        for (int r = 0; r < rings; r++)
        {
            for (int p = 0; p < segments; p++)
            {
                int current = p + r * (segments + 1) + vertexOffset;
                int next = p + 1 + r * (segments + 1) + vertexOffset;
                int currentBottom = p + (r + 1) * (segments + 1) + vertexOffset;
                int nextBottom = p + 1 + (r + 1) * (segments + 1) + vertexOffset;
                triangles[ti++] = current;
                triangles[ti++] = next;
                triangles[ti++] = nextBottom;
                triangles[ti++] = current;
                triangles[ti++] = nextBottom;
                triangles[ti++] = currentBottom;
            }
        }

        return ti;
    }

    private static int generateSphereUpperCapTriangles(int segments, int[] triangles, int ti)
    {
        for (int p = 0; p < segments; p++)
        {
            triangles[ti++] = p + 2;
            triangles[ti++] = p + 1;
            triangles[ti++] = 0;
        }

        return ti;
    }

    private static void generateSphereLowerCapTriangles(int segments, int rings, int[] triangles, int ti)
    {
        int lastVertex = 1 + (segments + 1) * (rings + 1);
        for (int p = 0; p < segments; p++)
        {
            triangles[ti++] = lastVertex;
            triangles[ti++] = lastVertex - (p + 2);
            triangles[ti++] = lastVertex - (p + 1);
        }
    }

    public static int[] generateCylindricalTriangles()
    {
        return generateCylindricalTriangles(SEGMENTS);
    }

    private static int[] generateCylindricalTriangles(int segments)
    {
        int circleTriangles = segments - 2;
        int[] triangles = new int[6 * (circleTriangles + (segments + 1))];
        int vi = 0;
        int ti = generateCircleTriangles(segments, triangles, vi, 0, false);
        ti = generateRingTriangles(segments, 1, triangles, segments + 1, ti);
        int vertexCount = (segments + 1) * 4;
        ti = generateCircleTriangles(segments, triangles, vertexCount - segments, ti, true);
        return triangles;
    }

    private static int generateCircleTriangles(int segments, int[] triangles, int vi, int ti, bool invert)
    {
        for (int p = 0; p < segments - 2; p++)
        {
            if (invert)
            {
                triangles[ti++] = vi;
                triangles[ti++] = vi + p + 1;
                triangles[ti++] = vi + p + 2;
            }
            else
            {
                triangles[ti++] = vi + p + 2;
                triangles[ti++] = vi + p + 1;
                triangles[ti++] = vi;
            }
        }

        return ti;
    }

    public static int getColorByNormal(float[] vertices, int v0, int v1, int v2)
    {
        Vector3f e0 = new Vector3f();
        Vector3f e1 = new Vector3f();
        Vector3f normal = new Vector3f();
        for (int j = 0; j < 3; ++j)
        {
            e0[j] = vertices[v1 + j] - vertices[v0 + j];
            e1[j] = vertices[v2 + j] - vertices[v0 + j];
        }

        normal[0] = e0[1] * e1[2] - e0[2] * e1[1];
        normal[1] = e0[2] * e1[0] - e0[0] * e1[2];
        normal[2] = e0[0] * e1[1] - e0[1] * e1[0];
        RecastVectors.normalize(ref normal);
        float c = clamp(0.57735026f * (normal[0] + normal[1] + normal[2]), -1, 1);
        int col = DebugDraw.duLerpCol(DebugDraw.duRGBA(32, 32, 0, 160), DebugDraw.duRGBA(220, 220, 0, 160),
            (int)(127 * (1 + c)));
        return col;
    }
}