using UnityEngine;

namespace KitsuneCafe.Entity
{
    public class Spring : MonoBehaviour
    {
        [SerializeField]
        private new Rigidbody rigidbody;

        [SerializeField]
        private float rayLength = 1.5f;

        [SerializeField]
        private float rideHeight = 1f;

        [SerializeField]
        private float rideSpringStrength;

        [SerializeField]
        private float rideSpringDamper;

        [SerializeField]
        private Vector3 rayOffset = Vector3.zero;

        [SerializeField]
        private Vector3 down = Vector3.down;

        [SerializeField]
        private LayerMask layerMask;

        private bool didRayHit;
        private Vector3 relativeDown;
        private float relativeVelocity;
        private float targetDistance;
        private float springForce;

        public bool IsSpringColliding => didRayHit;

        private void Reset()
        {
            rigidbody = GetComponent<Rigidbody>();
            layerMask = LayerMask.NameToLayer("Default");
        }

        private void FixedUpdate()
        {
            relativeDown = transform.TransformDirection(this.down);

            didRayHit = Raycast(relativeDown, out var hit);

            if (didRayHit)
            {
                relativeVelocity = RelativeVelocity(down, rigidbody, hit.rigidbody);
                targetDistance = hit.distance - rideHeight;
                springForce = (targetDistance * rideSpringStrength) - (relativeVelocity * rideSpringDamper);
                rigidbody.AddForce(relativeDown * springForce);
            }
        }

        private bool Raycast(Vector3 direction, out RaycastHit hit)
        {
            return Physics.Raycast(
                transform.position + rayOffset,
                direction,
                out hit,
                rayLength,
                layerMask
            );
        }

        private static float RelativeVelocity(Vector3 direction, Rigidbody a, Rigidbody b)
        {
            var aVelocity = GetVelocity(a);
            var bVelocity = GetVelocity(b);

            var aDirVelocity = Vector3.Dot(direction, aVelocity);
            var bDirVelocity = Vector3.Dot(direction, bVelocity);

            return aDirVelocity - bDirVelocity;

        }

        private static Vector3 GetVelocity(Rigidbody rb)
        {
            return rb == null ? Vector3.zero : rb.linearVelocity;
        }

        private void OnDrawGizmos()
        {
            var origin = transform.position + rayOffset;

            Gizmos.color = didRayHit ? Color.yellow : Color.red;
            Gizmos.DrawRay(origin, relativeDown * rayLength);

            if (didRayHit)
            {
                Gizmos.color = Color.green;
                Gizmos.DrawRay(origin, relativeDown * rideHeight);
            }
        }
    }
}
