using KdTree.Math;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Pulse : MonoBehaviour
{

    private static Vector3[] randomSpherePoints = null;
    private static KdTree.KdTree<float, int> randomSphereKd = new KdTree.KdTree<float, int>(3, new SphereMath());
    private static Dictionary<int, List<Vector3>> separatedPoints = new Dictionary<int, List<Vector3>>();

    private class SphereMath : FloatMath
    {
        public override float MinValue => -1;

        public override float MaxValue => 1;

        public override float DistanceSquaredBetweenPoints(float[] a, float[] b)
        {
            Vector3 normA = new Vector3(a[0], a[1], a[2]);
            Vector3 normB = new Vector3(b[0], b[1], b[2]);
            return Mathf.Atan2(Vector3.Cross(normA, normB).magnitude, Vector3.Dot(normA, normB));
        }
    }

    private static int randomSpherePointsCount = 300;
    private static void GenerateRandomPoints()
    {
        if (randomSpherePoints != null) return;

        randomSpherePoints = new Vector3[randomSpherePointsCount];
        for (int i = 0; i < randomSpherePointsCount; i++)
        {
            bool ok = false;
            while (!ok)
            {
                Vector3 r = UnityEngine.Random.insideUnitSphere;
                if (r.sqrMagnitude > 0.000001f)
                {
                    ok = true;

                    r.Normalize();
                    randomSpherePoints[i] = r;
                    randomSphereKd.Add(new float[] { r.x, r.y, r.z }, 0);
                }
            }
        }
    }

    /*
    private class KdTreePoints : IEnumerable<Vector3>
    {
        private class KdTreeEnumerator : IEnumerator<Vector3>
        {
            public Vector3 Current => new Vector3(underlying.Current.Point[0], underlying.Current.Point[1], underlying.Current.Point[2]);
            object IEnumerator.Current => Current;
            public void Dispose() => underlying.Dispose();
            public bool MoveNext() => underlying.MoveNext();
            public void Reset() => underlying.Reset();

            private IEnumerator<KdTree.KdTreeNode<float, int>> underlying;
            public KdTreeEnumerator(KdTree.KdTree<float, int> tree)
            {
                this.underlying = tree.GetEnumerator();
            }
        }
        private KdTree.KdTree<float, int> tree;
        public KdTreePoints(KdTree.KdTree<float, int> tree)
        {
            this.tree = tree;
        }

        public IEnumerator<Vector3> GetEnumerator() => new KdTreeEnumerator(tree);
        IEnumerator IEnumerable.GetEnumerator() => new KdTreeEnumerator(tree);
    }
    */

    private const float R1_SPHERE_AREA = 4 * Mathf.PI;

    private static float ArcLengthOfArea(float area)
    {
        // area = 2πd^2(1−cos(l / d)), where d = 1 is the sphere radius, and l is the arclength (disk radius)

        // area = 2π(1-cos(l))
        // area = 2π-2πcos(l)
        // 2π-area=2πcos(l)
        // (2π-area)/2π=cos(l)
        // 1-(area/2π)=cos(l)
        // l = acos(1-area/2π)

        return Mathf.Acos(1 - area / (Mathf.PI * 2));
    }

    private static IEnumerable<Vector3> GetNumRandomPointsOnSphere(int numPoints)
    {
        if (separatedPoints.ContainsKey(numPoints)) return separatedPoints[numPoints];

        GenerateRandomPoints();

        HashSet<Vector3> remaining = new HashSet<Vector3>(randomSpherePoints);
        List<Vector3> points = new List<Vector3>();

        float areaPerPoint = R1_SPHERE_AREA / numPoints;
        float arclengthPerPoint = ArcLengthOfArea(areaPerPoint);

        while (points.Count < numPoints && remaining.Count > 0)
        {
            int random = Random.Range(0, remaining.Count);
            foreach (Vector3 r in remaining)
            {
                if (random == 0)
                {
                    points.Add(r);

                    remaining.Remove(r);

                    foreach (KdTree.KdTreeNode<float, int> close in randomSphereKd.RadialSearch(new float[] { r.x, r.y, r.z }, arclengthPerPoint))
                    {
                        remaining.Remove(new Vector3(close.Point[0], close.Point[1], close.Point[2]));
                    }

                    break;
                }
                else
                {
                    random--;
                }
            }
        }

        separatedPoints[numPoints] = points;
        return separatedPoints[numPoints];
    }


    public float Lobes = 10;
    public float TimeScale = 1;
    public float Amount = 0.6f;

    private Mesh mesh;
    private Vector3[] originalPoints;
    private Vector3[] originalNormals;
    private Vector3[] points;
    // Start is called before the first frame update
    void Start()
    {
        MeshFilter mf = GetComponent<MeshFilter>();

        //mesh = GameObject.Instantiate(mf.mesh);
        //mf.mesh = mesh;

        mesh = mf.mesh;
        

        mesh.RecalculateNormals();

        originalPoints = mesh.vertices;
        originalNormals = mesh.normals;
        points = new Vector3[originalPoints.Length];
    }

    // Update is called once per frame
    void Update()
    {
        for (int i = 0; i < originalPoints.Length; i++)
        {
            Vector3 o = originalPoints[i];
            Vector3 n = originalNormals[i];

            float angleUp = Mathf.Atan2(new Vector2(o.x, o.z).magnitude, o.y);

            float expand = Mathf.Sin(Mathf.Atan2(o.x, o.z) * Mathf.Round(Lobes * angleUp) * Mathf.Sin(angleUp * Lobes) + Time.realtimeSinceStartup * Mathf.PI * TimeScale) * Amount;
            points[i] = o + n * expand;
        }

        mesh.vertices = points;
        mesh.RecalculateNormals();
        mesh.RecalculateBounds();
    }
}
