using System.Collections;
using UnityEngine;

public class AntaresBezierController : MonoBehaviour
{
    public int curvedPontsCount = 100;
    public Vector3[] wayPoints;
    public bool hideGizmos = false;
    public Vector3[] points;
    public AntaresCurvePoint[] waypointsGiz;

    public float spiralHeight = 5f;
    public float radiusD = 2f;
    public float radiusU = 4f;
    public float oborots = 2.5f;
    public float pointCount = 30;
    public bool spiralPropFoldout = false;
    public bool CW = true; 

    void OnDrawGizmosSelected()
    {
        bool unHide = false;
        if (hideGizmos)
        {
            hideGizmos = false;
            unHide = true;
        }
        DrawGizmos();
        if (unHide) hideGizmos = true;
    }

    /// <summary>
    /// Use CURVE class to control Curve
    /// </summary>
    /// <param name="position"></param>
    public Vector3 PointOnCurve(float position)
    {
        return Bezier.Beziercurve(points, position);
    }

    /// <summary>
    /// Use CURVE class to control Curve
    /// </summary>
    /// <param name="position"></param>
    public void ResetCurveParameters()
    {
        spiralHeight = 5f;
        radiusD = 2f;
        radiusU = 4f;
        oborots = 2.5f;
        pointCount = 30;
        curvedPontsCount = 100;
        RefreshLineRenderers();
    }

    /// <summary>
    /// Use CURVE class to control Curve
    /// </summary>
    /// <param name="position"></param>
    public void ClearCurve()
    {
        AntaresCurvePoint[] tr = transform.GetComponentsInChildren<AntaresCurvePoint>();
        foreach (AntaresCurvePoint t in tr)
        {
            if (Application.isEditor)
            {
                DestroyImmediate(t.gameObject);
                continue;
            }
            Destroy(t.gameObject);
        }
    }

    /// <summary>
    /// Use CURVE class to control Curve
    /// </summary>
    /// <param name="position"></param>
    public void InvertCurve()
    {
        Transform[] childs = transform.GetComponentsInChildren<Transform>();
        transform.DetachChildren();
        int count = childs.Length-1;
        for (int i = count; i >= 0; i--) childs[i].parent = transform;
    }

    /// <summary>
    /// Use CURVE class to control Curve
    /// </summary>
    /// <param name="position"></param>
    public void CreateSpiral()
    {
        ClearCurve();

        Vector3 startPoint = transform.position;
        Transform[] trs = GetComponentsInChildren<Transform>();
        if (trs.Length > 0)
        {
            startPoint = trs[trs.Length - 1].position;
        }
        GameObject goSpiralCenter = new GameObject("goSpiralCenter");
        goSpiralCenter.transform.position = startPoint + new Vector3(0, 0, radiusD);

        if (pointCount <= 1)
        {
            return;
        }
        float allGraduses = oborots * 360;
        float deltaGrad = allGraduses / (pointCount - 1);
        float curAngle = 0f;
        float deltaH = spiralHeight / (pointCount - 1);
        float deltaR = (radiusU - radiusD) / (pointCount - 1);

        for (int i = 1; i <= pointCount; i++)
        {
            curAngle = deltaGrad * i;
            GameObject newPoint = AddCurvePoint(startPoint);
            newPoint.transform.Translate(0, 0, -deltaR * i);
            newPoint.transform.parent = goSpiralCenter.transform;
            float ang = curAngle;
            if (!CW) ang = -curAngle;
            goSpiralCenter.transform.Rotate(0, ang, 0);
            newPoint.transform.parent = transform;

            newPoint.transform.Translate(0, deltaH * i, 0);
        }
        if (Application.isEditor && !Application.isPlaying) GameObject.DestroyImmediate(goSpiralCenter);
        else Destroy(goSpiralCenter);
    }

    /// <summary>
    /// Use CURVE class to control Curve
    /// </summary>
    /// <param name="position"></param>
    public void AddCurvePointDuringRuntime(Vector3 position)
    {
        GameObject point = new GameObject("Point", typeof(AntaresAddon), typeof(AntaresCurvePoint));
        point.transform.position = position;
        InvertCurve();
        point.transform.parent = transform;
        InvertCurve();
    }

    /// <summary>
    /// Use CURVE class to control Curve
    /// </summary>
    /// <param name="position"></param>
    protected GameObject AddCurvePoint(Vector3 position)
    {
        GameObject point = new GameObject("Point", typeof(AntaresAddon), typeof(AntaresCurvePoint));
        point.transform.position = position;
        point.transform.parent = transform;
        return point;
    }

    /// <summary>
    /// Use CURVE class to control Curve
    /// </summary>
    /// <param name="position"></param>
    public void RefreshCurve()
    {
        wayPoints = new Vector3[1];
        Component[] com = gameObject.GetComponentsInChildren(typeof(AntaresCurvePoint));
        waypointsGiz = new AntaresCurvePoint[com.Length];
        com.CopyTo(waypointsGiz, 0);
        int count = com.Length;
        points = new Vector3[count];
        float step = 1f / curvedPontsCount;

        ArrayList list = new ArrayList();

        int currentIndex = 0;
        Over30(waypointsGiz, currentIndex, ref list);
        ArrayList smoothedGroup = new ArrayList();
        currentIndex = 0;

        foreach (ArrayList l in list)
        {
            int ac = l.Count;
            Vector3[] v3 = new Vector3[ac];
            for (int i = 0; i < ac; i++) v3[i] = (Vector3)l[i];
            float onePercent = curvedPontsCount;
            int newCurvedPC = (int)(ac * (onePercent / 30));
            step = 1f / newCurvedPC;
            for (float t = 0; t < 1f; t += step)smoothedGroup.Add(Bezier.Beziercurve(v3, t));
        }
        Interpolation(ref wayPoints, smoothedGroup);
        FinishAndDraw();
        RefreshLineRenderers();
    }

    void FinishAndDraw()
    {
        if (Application.isPlaying) return;
        int count = wayPoints.Length;
        for (int i = 1; i < count; i++ )Gizmos.DrawIcon(wayPoints[i], Curve.pathToGizmos + "WaypointHeading.tif");
    }

    void Interpolation(ref Vector3[] result, ArrayList current)
    {
        int lenght = current.Count, l1 = result.Length;
        int allCount = l1 + current.Count;
        int startIndex = l1;
        if (l1 == 1)
        {
            --allCount;
            startIndex = 0;
        }
        Vector3[] res = new Vector3[allCount];
        if(l1 > 1)result.CopyTo(res, 0);
        for (int i = startIndex; i < allCount; i++)
        {
            res[i] = (Vector3)current[--lenght];
        }
        result = res;
    }

    void Over30(AntaresCurvePoint[] ways, int index, ref ArrayList holder)
    {
        ArrayList lst = new ArrayList();
        int lenght = ways.Length, step = 0;
        for(int i = 0; i < 30; i++)
        {
            step = i + index;
            if (step < lenght) lst.Add(ways[step].transform.position);
            else break;
        }
        holder.Add(lst);
        if (step < lenght)
        {
            Over30(ways, step, ref holder);
        }
    }

    /// <summary>
    /// Use CURVE class to control Curve
    /// </summary>
    /// <param name="position"></param>
    public void DrawGizmos()
    {
        if (hideGizmos) return;
        RefreshCurve();

        DrawWay();
        DrawLines(waypointsGiz);

        if (wayPoints.Length > 0)
        {
            Gizmos.DrawIcon(wayPoints[0], Curve.pathToGizmos + "Start.tif");
            Gizmos.DrawIcon(wayPoints[wayPoints.Length - 1], Curve.pathToGizmos + "End.tif");
        }
    }

    public float GetCurveLenght()
    {
        Vector3[] p = wayPoints;
        int count = p.Length - 1;
        float curveLength = 0;
        for (int i = 0; i < count; i++) curveLength += Vector3.Distance(p[i], p[i + 1]);
        return curveLength;
    }

    void DrawWay()
    {
        int count = wayPoints.Length;
        if (wayPoints.Length < 2) return;
        for(int i = 0; i < count; i++)
        {
            if (i == count - 1) break;
            Gizmos.color = Color.yellow;
            if(count > i + 1)Gizmos.DrawLine(wayPoints[i], wayPoints[i + 1]);
        }
    }

    void DrawLines(AntaresCurvePoint[] wp)
    {
        int count = wp.Length - 1;
        for (int i = 0; i < count; i++)
        {
            if (count == 1) break;
            Gizmos.color = Color.cyan;
            Gizmos.DrawLine(wp[i].transform.position, wp[i + 1].transform.position);
        }
    }

    public class Bezier
    {
        public static Vector3 Beziercurve(Vector3[] points, float positionOnCurve)
        {
            int i = 0;
            int c = 0;
            double p = 0;
            double t = positionOnCurve;
            int n = points.Length - 1;

            double[] xa = new double[n + 1];
            double[] ya = new double[n + 1];

            double x = 0, y = 0;

            for (i = 0; i <= n; i++)
            {
                xa[i] = points[i].x;
                ya[i] = points[i].z;
            }

            c = 1;
            for (i = 0; i <= n; i++)
            {
                if (i == 30) c = 1;
                xa[i] = xa[i] * c;
                ya[i] = ya[i] * c;
                c = (n - i) * c / (i + 1);
            }
            p = 1;
            for (i = 0; i <= n; i++)
            {
                if (i == 30) p = 1;
                xa[i] = xa[i] * p;
                ya[i] = ya[i] * p;
                p = p * t;
            }
            p = 1;
            for (i = n; i >= 0; i--)
            {
                if (i == 30) p = 1;
                xa[i] = xa[i] * p;
                ya[i] = ya[i] * p;
                p = p * (1 - t);
            }
            x = 0;
            y = 0;
            for (i = 0; i <= n; i++)
            {
                x = x + xa[i];
                y = y + ya[i];
            }

            return new Vector3((float)x, CalculateYaxis(points, positionOnCurve), (float)y);
        }

        private static float CalculateYaxis(Vector3[] points, float positionOnCurve)
        {
            int i = 0;
            int c = 0;
            double p = 0;
            double t = positionOnCurve;
            int n = points.Length - 1;

            double[] ya = new double[n + 1];

            double y = 0;

            for (i = 0; i <= n; i++) ya[i] = points[i].y;

            c = 1;
            for (i = 0; i <= n; i++)
            {
                if (i == 30) c = 1;
                ya[i] = ya[i] * c;
                c = (n - i) * c / (i + 1);
            }
            p = 1;
            for (i = 0; i <= n; i++)
            {
                if (i == 30) p = 1;
                ya[i] = ya[i] * p;
                p = p * t;
            }
            p = 1;
            for (i = n; i >= 0; i--)
            {
                if (i == 30) p = 1;
                ya[i] = ya[i] * p;
                p = p * (1 - t);
            }
            y = 0;
            for (i = 0; i <= n; i++)
            {
                y = y + ya[i];
            }
            return (float)y;
        }
    }

    public void SetLineRenderer(LineRenderer lr)
    {
        Component[] c = gameObject.GetComponentsInChildren<AntaresCurvePoint>();
        lr.SetVertexCount(c.Length);
        lr.useWorldSpace = true;
        int index = 0;
        foreach (AntaresCurvePoint cp in c)
        {
            lr.SetPosition(index++, cp.transform.position);
        }
    }

    public void SetLineRendererForCurve(LineRenderer lr)
    {
        int index = wayPoints.Length;
        lr.SetVertexCount(index);
        lr.useWorldSpace = true;
        for (int i = 0; i < index; i++) lr.SetPosition(i, wayPoints[i]);
    }

    public void RefreshLineRenderers()
    {
        if (lineRendererCurve != null) SetLineRendererForCurve(lineRendererCurve);
        if (lineRendererControl != null) SetLineRenderer(lineRendererControl);
    }

    public LineRenderer lineRendererCurve, lineRendererControl;
    void Start()
    {
        RefreshLineRenderers();
    }
}

