using System.Collections.Generic;
using UnityEngine;
namespace ARLocation
{
    public enum SplineType
    {
        CatmullromSpline,
        LinearSpline,
    }
    public abstract class Spline
    {
        /// 
        /// The points interpolated of the spline.
        /// 
        public Vector3[] Points { get; protected set; }
        /// 
        /// The CatmullRom curve-segments of the spline.
        /// 
        protected Curve[] segments;
        /// 
        /// The number of segments that make up the spline.
        /// 
        protected int segmentCount = 0;
        /// 
        /// The full (estimated) length of the spline.
        /// 
        public float Length { get; protected set; }
        protected float[] lengths;
        /// 
        /// Calculate the catmull-rom segments. Also estimates the curve's length.
        /// 
        /// The number sample points used to estimate each segment's length.
        public abstract void CalculateSegments(int n);
        /// 
        /// Returns the point of the spline at a given arc-length.
        /// 
        /// The arc-length.
        /// 
        public Vector3 GetPointAtArcLength(float s)
        {
            s = Mathf.Clamp(s, 0, Length);
            for (var i = 0; i < segmentCount; i++)
            {
                if (s <= lengths[i])
                {
                    var offset = i == 0 ? 0 : lengths[i - 1];
                    return segments[i].GetPointAtLength(s - offset);
                }
            }
            return segments[segmentCount - 1].GetPoint(1);
        }
        /// 
        /// Returns a CurvePointData whith the point and tangent of the spline
        /// at a given arc-length.
        /// 
        /// The arc-length.
        /// 
        public CurvePointData GetPointAndTangentAtArcLength(float s)
        {
            s = Mathf.Clamp(s, 0, Length);
            for (var i = 0; i < segmentCount; i++)
            {
                if (s <= lengths[i])
                {
                    var offset = i == 0 ? 0 : lengths[i - 1];
                    return segments[i].GetPointAndTangentAtLength(s - offset);
                }
            }
            return segments[segmentCount - 1].GetPointAndTangentAtLength(1);
        }
        /// 
        /// Draws the curve using a given LineRenderer, with points being processed by a given
        /// function beforehand.
        /// 
        /// 
        /// 
        /// 
        public void DrawCurveWithLineRenderer(LineRenderer renderer, System.Func func, int n = 100)
        {
            var points = new List();
            float s = 0.0f;
            while (s <= Length)
            {
                var pointData = GetPointAndTangentAtArcLength(s);
                points.Add(func(pointData.point));
                s += Length / (n + 1.0f);
            }
            var arr = points.ToArray();
            renderer.positionCount = arr.Length;
            renderer.SetPositions(arr);
        }
        /// 
        /// Calculates a sample of (N+2) equidistant points along the spline.
        /// 
        /// The number of points in the sample will be (N+2).
        /// A function that can be used to transform the sampled poins.
        /// 
        public Vector3[] SamplePoints(int n, System.Func func)
        {
            var sample = new Vector3[n + 2];
            var delta = Length / (n + 1.0f);
            var s = 0.0f;
            for (var i = 0; i < (n + 2); i++)
            {
                sample[i] = func(GetPointAtArcLength(s));
                s += delta;
            }
            return sample;
        }
        /// 
        /// Calculates a sample of (N+2) equidistant points along the spline.
        /// 
        /// The number of points in the sample will be (N+2).
        /// 
        public Vector3[] SamplePoints(int n)
        {
            return SamplePoints(n, (p) => p);
        }
        /// 
        /// Draw the curve and sample point using Gizmos.
        /// 
        public void DrawGizmos()
        {
            DrawPointsGizmos();
            DrawCurveLengthGizmos();
        }
        private void DrawPointsGizmos()
        {
            foreach (var p in Points)
            {
                Gizmos.color = Color.blue;
                Gizmos.DrawSphere(p, 0.1f);
            }
        }
        private void DrawCurveLengthGizmos()
        {
            var p = GetPointAtArcLength(0f);
            float s = 0.0f;
            while (s <= Length)
            {
                Gizmos.color = Color.green;
                var pointData = GetPointAndTangentAtArcLength(s);
                Vector3 n = pointData.point;
                Gizmos.DrawLine(p, n);
                p = n;
                s += 0.1f;
                Gizmos.color = Color.magenta;
                var tan = pointData.tangent;
                Gizmos.color = Color.blue;
                Gizmos.DrawLine(n, n + tan);
            }
        }
    }
}