// Chest|Controls3D|100030
namespace VRTK
{
    using UnityEngine;

    /// <summary>
    /// Transforms a game object into a chest with a lid. The direction can be auto-detected with very high reliability or set manually.
    /// </summary>
    /// <remarks>
    /// The script will instantiate the required Rigidbody, Interactable and HingeJoint components automatically in case they do not exist yet. It will expect three distinct game objects: a body, a lid and a handle. These should be independent and not children of each other.
    /// </remarks>
    /// <example>
    /// `VRTK/Examples/025_Controls_Overview` shows a chest that can be open and closed, it also displays the current opening angle of the chest.
    /// </example>
    [AddComponentMenu("VRTK/Scripts/Controls/3D/VRTK_Chest")]
    [System.Obsolete("`VRTK.VRTK_Chest` has been deprecated and can be recreated with `VRTK.Controllables.PhysicsBased.VRTK_PhysicsRotator`. This script will be removed in a future version of VRTK.")]
    public class VRTK_Chest : VRTK_Control
    {
        [Tooltip("The axis on which the chest should open. All other axis will be frozen.")]
        public Direction direction = Direction.autodetect;
        [Tooltip("The game object for the lid.")]
        public GameObject lid;
        [Tooltip("The game object for the body.")]
        public GameObject body;
        [Tooltip("The game object for the handle.")]
        public GameObject handle;
        [Tooltip("The parent game object for the chest content elements.")]
        public GameObject content;
        [Tooltip("Makes the content invisible while the chest is closed.")]
        public bool hideContent = true;
        [Tooltip("The maximum opening angle of the chest.")]
        public float maxAngle = 160f;

        protected float minAngle = 0f;
        protected float stepSize = 1f;
        protected Rigidbody bodyRigidbody;
        protected Rigidbody handleRigidbody;
        protected FixedJoint handleJoint;
        protected Rigidbody lidRigidbody;
        protected HingeJoint lidJoint;
        protected bool lidJointCreated;
        protected Direction finalDirection;
        protected float subDirection = 1; // positive or negative can be determined automatically since handle dictates that

        protected override void OnDrawGizmos()
        {
            base.OnDrawGizmos();
            if (!enabled || !setupSuccessful)
            {
                return;
            }

            // show opening direction
            Bounds bounds;
            if (handle)
            {
                bounds = VRTK_SharedMethods.GetBounds(handle.transform, handle.transform);
            }
            else
            {
                bounds = VRTK_SharedMethods.GetBounds(lid.transform, lid.transform);
            }
            float length = bounds.extents.y * 5f;
            Vector3 point = bounds.center + new Vector3(0, length, 0);
            switch (finalDirection)
            {
                case Direction.x:
                    point += transform.right.normalized * (length / 2f) * subDirection;
                    break;
                case Direction.y:
                    point += transform.up.normalized * (length / 2f) * subDirection;
                    break;
                case Direction.z:
                    point += transform.forward.normalized * (length / 2f) * subDirection;
                    break;
            }

            Gizmos.DrawLine(bounds.center + new Vector3(0, bounds.extents.y, 0), point);
            Gizmos.DrawSphere(point, length / 8f);
        }

        protected override void InitRequiredComponents()
        {
            InitBody();
            InitLid();
            InitHandle();

            SetContent(content, hideContent);
        }

        protected override bool DetectSetup()
        {
            if (lid == null || body == null)
            {
                return false;
            }

            finalDirection = (direction == Direction.autodetect) ? DetectDirection() : direction;
            if (finalDirection == Direction.autodetect)
            {
                return false;
            }

            Bounds lidBounds = VRTK_SharedMethods.GetBounds(lid.transform, transform);

            // determin sub-direction depending on handle
            if (handle)
            {
                Bounds handleBounds = VRTK_SharedMethods.GetBounds(handle.transform, transform);
                switch (finalDirection)
                {
                    case Direction.x:
                        subDirection = (handleBounds.center.x > lidBounds.center.x) ? -1 : 1;
                        break;
                    case Direction.y:
                        subDirection = (handleBounds.center.y > lidBounds.center.y) ? -1 : 1;
                        break;
                    case Direction.z:
                        subDirection = (handleBounds.center.z > lidBounds.center.z) ? -1 : 1;
                        break;
                }

                // handle should be outside lid hierarchy, otherwise anchor-by-bounds calculation is off
                if (handle.transform.IsChildOf(lid.transform))
                {
                    return false;
                }
            }
            else
            {
                subDirection = -1;
            }
            if (lidJointCreated)
            {
                lidJoint.useLimits = true;
                lidJoint.enableCollision = true;

                JointLimits limits = lidJoint.limits;
                switch (finalDirection)
                {
                    case Direction.x:
                        lidJoint.anchor = new Vector3(subDirection * lidBounds.extents.x, 0, 0);
                        lidJoint.axis = new Vector3(0, 0, 1);
                        if (subDirection > 0)
                        {
                            limits.min = -maxAngle;
                            limits.max = minAngle;
                        }
                        else
                        {
                            limits.min = minAngle;
                            limits.max = maxAngle;
                        }
                        break;
                    case Direction.y:
                        lidJoint.anchor = new Vector3(0, subDirection * lidBounds.extents.y, 0);
                        lidJoint.axis = new Vector3(0, 1, 0);
                        if (subDirection > 0)
                        {
                            limits.min = -maxAngle;
                            limits.max = minAngle;
                        }
                        else
                        {
                            limits.min = minAngle;
                            limits.max = maxAngle;
                        }
                        break;
                    case Direction.z:
                        lidJoint.anchor = new Vector3(0, 0, subDirection * lidBounds.extents.z);
                        lidJoint.axis = new Vector3(1, 0, 0);
                        if (subDirection < 0)
                        {
                            limits.min = -maxAngle;
                            limits.max = minAngle;
                        }
                        else
                        {
                            limits.min = minAngle;
                            limits.max = maxAngle;
                        }
                        break;
                }
                lidJoint.limits = limits;
            }
            return true;
        }

        protected override ControlValueRange RegisterValueRange()
        {
            return new ControlValueRange()
            {
                controlMin = lidJoint.limits.min,
                controlMax = lidJoint.limits.max
            };
        }

        protected override void HandleUpdate()
        {
            value = CalculateValue();
        }

        protected virtual Direction DetectDirection()
        {
            Direction returnDirection = Direction.autodetect;

            if (!handle)
            {
                return returnDirection;
            }

            Bounds handleBounds = VRTK_SharedMethods.GetBounds(handle.transform, transform);
            Bounds lidBounds = VRTK_SharedMethods.GetBounds(lid.transform, transform);

            float lengthX = Mathf.Abs(handleBounds.center.x - (lidBounds.center.x + lidBounds.extents.x));
            float lengthZ = Mathf.Abs(handleBounds.center.z - (lidBounds.center.z + lidBounds.extents.z));
            float lengthNegX = Mathf.Abs(handleBounds.center.x - (lidBounds.center.x - lidBounds.extents.x));
            float lengthNegZ = Mathf.Abs(handleBounds.center.z - (lidBounds.center.z - lidBounds.extents.z));

            if (VRTK_SharedMethods.IsLowest(lengthX, new float[] { lengthZ, lengthNegX, lengthNegZ }))
            {
                returnDirection = Direction.x;
            }
            else if (VRTK_SharedMethods.IsLowest(lengthNegX, new float[] { lengthX, lengthZ, lengthNegZ }))
            {
                returnDirection = Direction.x;
            }
            else if (VRTK_SharedMethods.IsLowest(lengthZ, new float[] { lengthX, lengthNegX, lengthNegZ }))
            {
                returnDirection = Direction.z;
            }
            else if (VRTK_SharedMethods.IsLowest(lengthNegZ, new float[] { lengthX, lengthZ, lengthNegX }))
            {
                returnDirection = Direction.z;
            }

            return returnDirection;
        }

        protected virtual void InitBody()
        {
            bodyRigidbody = body.GetComponent<Rigidbody>();
            if (bodyRigidbody == null)
            {
                bodyRigidbody = body.AddComponent<Rigidbody>();
                bodyRigidbody.isKinematic = true; // otherwise body moves/falls over when lid is moved or fully open
            }
        }

        protected virtual void InitLid()
        {
            lidRigidbody = lid.GetComponent<Rigidbody>();
            if (lidRigidbody == null)
            {
                lidRigidbody = lid.AddComponent<Rigidbody>();
            }

            lidJoint = lid.GetComponent<HingeJoint>();
            if (lidJoint == null)
            {
                lidJoint = lid.AddComponent<HingeJoint>();
                lidJointCreated = true;
            }
            lidJoint.connectedBody = bodyRigidbody;

            if (!handle)
            {
                CreateInteractableObject(lid);
            }
        }

        protected virtual void InitHandle()
        {
            if (!handle)
            {
                return;
            }
            handleRigidbody = handle.GetComponent<Rigidbody>();
            if (handleRigidbody == null)
            {
                handleRigidbody = handle.AddComponent<Rigidbody>();
            }
            handleRigidbody.isKinematic = false;
            handleRigidbody.useGravity = false;

            handleJoint = handle.GetComponent<FixedJoint>();
            if (handleJoint == null)
            {
                handleJoint = handle.AddComponent<FixedJoint>();
                handleJoint.connectedBody = lidRigidbody;
            }

            CreateInteractableObject(handle);
        }

        protected virtual void CreateInteractableObject(GameObject targetGameObject)
        {
            VRTK_InteractableObject targetInteractableObject = targetGameObject.GetComponent<VRTK_InteractableObject>();
            if (targetInteractableObject == null)
            {
                targetInteractableObject = targetGameObject.AddComponent<VRTK_InteractableObject>();
            }
            targetInteractableObject.isGrabbable = true;
            targetInteractableObject.grabAttachMechanicScript = gameObject.AddComponent<GrabAttachMechanics.VRTK_TrackObjectGrabAttach>();
            targetInteractableObject.secondaryGrabActionScript = gameObject.AddComponent<SecondaryControllerGrabActions.VRTK_SwapControllerGrabAction>();
            targetInteractableObject.grabAttachMechanicScript.precisionGrab = true;
            targetInteractableObject.stayGrabbedOnTeleport = false;
        }

        protected virtual float CalculateValue()
        {
            return (Mathf.Round((minAngle + Mathf.Clamp01(Mathf.Abs(lidJoint.angle / (lidJoint.limits.max - lidJoint.limits.min))) * (maxAngle - minAngle)) / stepSize) * stepSize);
        }
    }
}