using System;
using System.Numerics;
namespace MoonTools.Curve
{
    /// 
    /// A 3-dimensional Bezier curve defined by 4 points.
    /// 
    public struct CubicBezierCurve3D : IEquatable
    {
        /// 
        /// The start point.
        /// 
        public Vector3 P0 { get; }
        /// 
        /// The first control point.
        /// 
        public Vector3 P1 { get; }
        /// 
        /// The second control point.
        /// 
        public Vector3 P2 { get; }
        /// 
        /// The end point.
        /// 
        public Vector3 P3 { get; }
        /// 
        /// A representation of a 3D cubic Bezier curve.
        /// 
        /// The start point.
        /// The first control point.
        /// The second control point.
        /// The end point.
        public CubicBezierCurve3D(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3)
        {
            P0 = p0;
            P1 = p1;
            P2 = p2;
            P3 = p3;
        }
        /// 
        /// Returns the curve coordinate given by t.
        /// 
        /// A value between 0 and 1.
        public Vector3 Point(float t) => Point(P0, P1, P2, P3, t);
        /// 
        /// Returns the curve coordinate given by a normalized time value.
        /// 
        /// 
        /// 
        /// 
        public Vector3 Point(float t, float startTime, float endTime) => Point(P0, P1, P2, P3, t, startTime, endTime);
        /// 
        /// Returns the instantaneous velocity on the curve given by t.
        /// 
        /// A value between 0 and 1.
        public Vector3 Velocity(float t) => Velocity(P0, P1, P2, P3, t);
        /// 
        /// Returns the instantaneous velocity on the curve given by a normalized time value.
        /// 
        /// A value between 0 and 1.
        /// 
        /// 
        public Vector3 Velocity(float t, float startTime, float endTime) => Velocity(P0, P1, P2, P3, t, startTime, endTime);
        /// 
        /// Returns the curve coordinate given by 4 points and a time value.
        /// 
        /// The start point.
        /// The first control point.
        /// The second control point.
        /// The end point.
        /// A value between 0 and 1.
        public static Vector3 Point(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t)
        {
            ArgumentChecker.CheckT(t);
            return ((1f - t) * (1f - t) * (1f - t) * p0) +
                    (3f * (1f - t) * (1f - t) * t * p1) +
                    (3f * (1f - t) * t * t * p2) +
                    (t * t * t * p3);
        }
        /// 
        /// Returns the curve coordinate given by 4 points and a normalized time value.
        /// 
        /// The start point.
        /// The first control point.
        /// The second control point.
        /// The end point.
        /// A value between startTime and endTime.
        /// 
        /// 
        public static Vector3 Point(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t, float startTime, float endTime)
        {
            ArgumentChecker.CheckT(t, startTime, endTime);
            return Point(p0, p1, p2, p3, TimeHelper.Normalized(t, startTime, endTime));
        }
        /// 
        /// Returns the instantaneous velocity given by 4 points and a time value.
        /// 
        /// The start point.
        /// The first control point.
        /// The second control point.
        /// The end point.
        /// A value between 0 and 1.
        public static Vector3 Velocity(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t)
        {
            ArgumentChecker.CheckT(t);
            return (3f * (1f - t) * (1f - t) * (p1 - p0)) +
                    (6f * (1f - t) * t * (p2 - p1)) +
                    (3f * t * t * (p3 - p2));
        }
        /// 
        /// Returns the instantaneous velocity given by 4 points and a normalized time value.
        /// 
        /// The start point.
        /// The first control point.
        /// The second control point.
        /// The end point.
        /// A value between startTime and endTime.
        /// 
        /// 
        public static Vector3 Velocity(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t, float startTime, float endTime)
        {
            ArgumentChecker.CheckT(t, startTime, endTime);
            return Velocity(p0, p1, p2, p3, TimeHelper.Normalized(t, startTime, endTime));
        }
        public override bool Equals(object obj)
        {
            return obj is CubicBezierCurve3D d && Equals(d);
        }
        public bool Equals(CubicBezierCurve3D other)
        {
            return P0.Equals(other.P0) &&
                   P1.Equals(other.P1) &&
                   P2.Equals(other.P2) &&
                   P3.Equals(other.P3);
        }
        public override int GetHashCode()
        {
            return HashCode.Combine(P0, P1, P2, P3);
        }
        public static bool operator ==(CubicBezierCurve3D left, CubicBezierCurve3D right)
        {
            return left.Equals(right);
        }
        public static bool operator !=(CubicBezierCurve3D left, CubicBezierCurve3D right)
        {
            return !(left == right);
        }
    }
}