// Headset Collision|Presence|70010
namespace VRTK
{
    using UnityEngine;

    /// <summary>
    /// Event Payload
    /// </summary>
    /// <param name="collider">The Collider of the game object the headset has collided with.</param>
    /// <param name="currentTransform">The current Transform of the object that the Headset Collision Fade script is attached to (Camera).</param>
    public struct HeadsetCollisionEventArgs
    {
        public Collider collider;
        public Transform currentTransform;
    }

    /// <summary>
    /// Event Payload
    /// </summary>
    /// <param name="sender">this object</param>
    /// <param name="e"><see cref="HeadsetCollisionEventArgs"/></param>
    public delegate void HeadsetCollisionEventHandler(object sender, HeadsetCollisionEventArgs e);

    /// <summary>
    /// Denotes when the HMD is colliding with valid geometry.
    /// </summary>
    /// <remarks>
    /// **Script Usage:**
    ///  * Place the `VRTK_HeadsetCollision` script on any active scene GameObject.
    /// </remarks>
    /// <example>
    /// `VRTK/Examples/011_Camera_HeadSetCollisionFading` has collidable walls around the play area and if the user puts their head into any of the walls then the headset will fade to black.
    /// </example>
    [AddComponentMenu("VRTK/Scripts/Presence/VRTK_HeadsetCollision")]
    public class VRTK_HeadsetCollision : MonoBehaviour
    {
        [Tooltip("If this is checked then the headset collision will ignore colliders set to `Is Trigger = true`.")]
        public bool ignoreTriggerColliders = false;
        [Tooltip("The radius of the auto generated sphere collider for detecting collisions on the headset.")]
        public float colliderRadius = 0.1f;
        [Tooltip("A specified VRTK_PolicyList to use to determine whether any objects will be acted upon by the Headset Collision.")]
        public VRTK_PolicyList targetListPolicy;

        /// <summary>
        /// Emitted when the user's headset collides with another game object.
        /// </summary>
        public event HeadsetCollisionEventHandler HeadsetCollisionDetect;
        /// <summary>
        /// Emitted when the user's headset stops colliding with a game object.
        /// </summary>
        public event HeadsetCollisionEventHandler HeadsetCollisionEnded;

        /// <summary>
        /// Determines if the headset is currently colliding with another object.
        /// </summary>
        [HideInInspector]
        public bool headsetColliding = false;
        /// <summary>
        /// Stores the collider of what the headset is colliding with.
        /// </summary>
        [HideInInspector]
        public Collider collidingWith = null;

        protected Transform headset;
        protected VRTK_HeadsetCollider headsetColliderScript;
        protected GameObject headsetColliderContainer;
        protected bool generateCollider = false;
        protected bool generateRigidbody = false;

        public virtual void OnHeadsetCollisionDetect(HeadsetCollisionEventArgs e)
        {
            if (HeadsetCollisionDetect != null)
            {
                HeadsetCollisionDetect(this, e);
            }
        }

        public virtual void OnHeadsetCollisionEnded(HeadsetCollisionEventArgs e)
        {
            if (HeadsetCollisionEnded != null)
            {
                HeadsetCollisionEnded(this, e);
            }
        }

        /// <summary>
        /// The IsColliding method is used to determine if the headset is currently colliding with a valid game object and returns true if it is and false if it is not colliding with anything or an invalid game object.
        /// </summary>
        /// <returns>Returns `true` if the headset is currently colliding with a valid game object.</returns>
        public virtual bool IsColliding()
        {
            return headsetColliding;
        }

        /// <summary>
        /// The GetHeadsetColliderContainer method returns the auto generated GameObject that contains the headset collider.
        /// </summary>
        /// <returns>The auto generated headset collider GameObject.</returns>
        public virtual GameObject GetHeadsetColliderContainer()
        {
            return headsetColliderContainer;
        }

        protected virtual void Awake()
        {
            VRTK_SDKManager.AttemptAddBehaviourToToggleOnLoadedSetupChange(this);
        }

        protected virtual void OnEnable()
        {
            headset = VRTK_DeviceFinder.HeadsetTransform();
            if (headset != null)
            {
                headsetColliding = false;
                SetupHeadset();
                VRTK_PlayerObject.SetPlayerObject(headsetColliderContainer.gameObject, VRTK_PlayerObject.ObjectTypes.Headset);
            }
        }

        protected virtual void OnDisable()
        {
            if (headset != null && headsetColliderScript != null)
            {
                headsetColliderScript.EndCollision(collidingWith);
                TearDownHeadset();
            }
        }

        protected virtual void OnDestroy()
        {
            VRTK_SDKManager.AttemptRemoveBehaviourToToggleOnLoadedSetupChange(this);
        }

        protected virtual void Update()
        {
            if (headsetColliderContainer != null && headsetColliderContainer.transform.parent != headset)
            {
                headsetColliderContainer.transform.SetParent(headset);
                headsetColliderContainer.transform.localPosition = Vector3.zero;
                headsetColliderContainer.transform.localRotation = headset.localRotation;
            }
        }

        protected virtual void CreateHeadsetColliderContainer()
        {
            if (headsetColliderContainer == null)
            {
                headsetColliderContainer = new GameObject(VRTK_SharedMethods.GenerateVRTKObjectName(true, "HeadsetColliderContainer"));
                headsetColliderContainer.transform.position = Vector3.zero;
                headsetColliderContainer.transform.localRotation = headset.localRotation;
                headsetColliderContainer.transform.localScale = Vector3.one;
                headsetColliderContainer.layer = LayerMask.NameToLayer("Ignore Raycast");
            }
        }

        protected virtual void SetupHeadset()
        {
            Rigidbody headsetRigidbody = headset.GetComponentInChildren<Rigidbody>();
            if (headsetRigidbody == null)
            {
                CreateHeadsetColliderContainer();
                headsetRigidbody = headsetColliderContainer.AddComponent<Rigidbody>();
                headsetRigidbody.constraints = RigidbodyConstraints.FreezeAll;
                generateRigidbody = true;
            }
            headsetRigidbody.isKinematic = true;
            headsetRigidbody.useGravity = false;

            Collider headsetCollider = headset.GetComponentInChildren<Collider>();
            if (headsetCollider == null)
            {
                CreateHeadsetColliderContainer();
                SphereCollider newCollider = headsetColliderContainer.gameObject.AddComponent<SphereCollider>();
                newCollider.radius = colliderRadius;
                headsetCollider = newCollider;
                generateCollider = true;
            }
            headsetCollider.isTrigger = true;

            if (headsetColliderScript == null)
            {
                GameObject attachTo = (headsetColliderContainer ? headsetColliderContainer : headset.gameObject);
                headsetColliderScript = attachTo.AddComponent<VRTK_HeadsetCollider>();
                headsetColliderScript.SetParent(gameObject);
                headsetColliderScript.SetIgnoreTarget(targetListPolicy);
            }
        }

        protected virtual void TearDownHeadset()
        {
            if (generateCollider)
            {
                Destroy(headset.gameObject.GetComponent<BoxCollider>());
            }
            if (generateRigidbody)
            {
                Destroy(headset.gameObject.GetComponent<Rigidbody>());
            }
            if (headsetColliderScript != null)
            {
                Destroy(headsetColliderScript);
            }
            if (headsetColliderContainer != null)
            {
                Destroy(headsetColliderContainer);
            }
        }
    }

    public class VRTK_HeadsetCollider : MonoBehaviour
    {
        protected VRTK_HeadsetCollision parent;
        protected VRTK_PolicyList targetListPolicy;

        public virtual void SetParent(GameObject setParent)
        {
            parent = setParent.GetComponent<VRTK_HeadsetCollision>();
        }

        public virtual void SetIgnoreTarget(VRTK_PolicyList list = null)
        {
            targetListPolicy = list;
        }

        public virtual void EndCollision(Collider collider)
        {
            if (collider == null || !VRTK_PlayerObject.IsPlayerObject(collider.gameObject))
            {
                parent.headsetColliding = false;
                parent.collidingWith = null;
                parent.OnHeadsetCollisionEnded(SetHeadsetCollisionEvent(collider, transform));
            }
        }

        protected virtual void OnTriggerStay(Collider collider)
        {
            if (parent.ignoreTriggerColliders && collider != null && collider.isTrigger)
            {
                return;
            }

            if (enabled && !VRTK_PlayerObject.IsPlayerObject(collider.gameObject) && ValidTarget(collider.transform))
            {
                parent.headsetColliding = true;
                parent.collidingWith = collider;
                parent.OnHeadsetCollisionDetect(SetHeadsetCollisionEvent(collider, transform));
            }
        }

        protected virtual void OnTriggerExit(Collider collider)
        {
            if (parent.ignoreTriggerColliders && collider != null && collider.isTrigger)
            {
                return;
            }

            EndCollision(collider);
        }

        protected virtual void Update()
        {
            if (parent.headsetColliding && (parent.collidingWith == null || !parent.collidingWith.gameObject.activeInHierarchy))
            {
                EndCollision(parent.collidingWith);
            }
        }

        protected virtual HeadsetCollisionEventArgs SetHeadsetCollisionEvent(Collider collider, Transform currentTransform)
        {
            HeadsetCollisionEventArgs e;
            e.collider = collider;
            e.currentTransform = currentTransform;
            return e;
        }

        protected virtual bool ValidTarget(Transform target)
        {
            return (target != null && !(VRTK_PolicyList.Check(target.gameObject, targetListPolicy)));
        }
    }
}