using System;
using UnityEngine;

namespace pdinklag.CPW.Common
{
    /// <summary>
    /// Defines a location relative to a body description.
    /// </summary>
    public class BodyLocation : MonoBehaviour
    {
        /// <summary>
        /// The mode for targeting the position to a body.
        /// </summary>
        public enum TargetingMode
        {
            /// <summary>
            /// The location is retargeted by computing an offset to a single POI.
            /// </summary>
            Point,

            /// <summary>
            /// The location is targeted using barycentric coordinates for a triangle of POIs.
            /// </summary>
            Triangle,
        }

        [Header("Definition")]
        [Tooltip("How the location is targeted to a body.")]
        public TargetingMode Mode;

        [Tooltip("The reference point or the first corner of the reference triangle.")]
        public BodyPOI Point1;

        [Tooltip("The second corner of the reference triangle.")]
        public BodyPOI Point2;

        [Tooltip("The third corner of the reference triangle.")]
        public BodyPOI Point3;

        // in Point mode, this stores the offset from the POI in relative coordinates
        // in Triangle mode, this stores the barycentric coordinates (XZ) and distance (Y)
        [Header("Precomputed Values")]
        [Tooltip("The precomputed offset from the reference point (offset) or triangle (barycentric coordinates).")]
        public Vector3 Offset;

#if UNITY_EDITOR
        [NonSerialized]
        public BodyDescription RefBody; // nb: used for editing
#endif

        /// <summary>
        /// Targets the location to a body.
        /// </summary>
        /// <param name="body">the body in question</param>
        public Vector3 Target(BodyDescription body)
        {
            switch (Mode)
            {
                default:
                case TargetingMode.Point:
                    var point = body.GetPOITransform(Point1);
                    return point.position + point.TransformDirection(Offset);

                case TargetingMode.Triangle:
                    var triSpace = new TriangleSpace(body.GetPOI(Point1), body.GetPOI(Point2), body.GetPOI(Point3));
                    return triSpace.Transform(Offset.ToDouble3());
            }
        }

        /// <summary>
        /// Computes the offset for a given reference body.
        /// </summary>
        /// <param name="body">the reference body</param>
        public Vector3 ComputeOffset(BodyDescription body)
        {
            var pos = transform.position;
            Vector3 offset;
            switch (Mode)
            {
                case TargetingMode.Point:
                    var point = body.GetPOITransform(Point1);
                    offset = (Vector4)point.InverseTransformDirection(pos - point.position);
                    break;

                case TargetingMode.Triangle:
                default:
                    var triSpace = new TriangleSpace(body.GetPOI(Point1), body.GetPOI(Point2), body.GetPOI(Point3));
                    offset = triSpace.InverseTransform(pos).ToVector3();
                    break;
            }
            return offset;
        }

#if UNITY_EDITOR
        private void OnDrawGizmosSelected()
        {
            if (RefBody)
            {
                switch (Mode)
                {
                    case TargetingMode.Point:
                        Gizmos.color = Color.green;
                        Gizmos.DrawLine(transform.position, RefBody.GetPOI(Point1));
                        break;

                    case TargetingMode.Triangle:
                        var a = RefBody.GetPOI(Point1);
                        var b = RefBody.GetPOI(Point2);
                        var c = RefBody.GetPOI(Point3);
                        var triSpace = new TriangleSpace(a, b, c);
                        var proj = triSpace.Project(transform.position).ToVector3();

                        Gizmos.color = Color.yellow;
                        Gizmos.DrawLine(a, b);
                        Gizmos.DrawLine(a, c);
                        Gizmos.DrawLine(b, c);
                        Gizmos.color = Color.green;
                        Gizmos.DrawLine(transform.position, proj);
                        break;
                }
            }
        }
#endif
    }
}
