// MIT License
// All additional code examples as shown in the iX article (Hololens / C.Zimmer, C.Geiger)
// will be provided for free under the MIT License.You are welcome to show your support
// by obtaining an iX magazine at the local store or online.
// DISCLAIMER:
// This component is solely intended to speed up the prototyping process of HoloLens applications.
// We do not recommend using it in a final product. No responsibility is taken for any potential
// damage caused by the use of this product.

using HoloToolkit.Unity.InputModule;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Events;
using System;
using UnityEngine.VR.WSA;

public class HoloInteractibleHandler : MonoBehaviour, IFocusable, IInputClickHandler, INavigationHandler, IManipulationHandler, IHoldHandler, IInputHandler
{
    [Header("Generally independent of user focus?")]
    [Tooltip("Defines if interactions should fire even if the object isn't focused by the user." +
    "CAUTION: This is independent from temporary global listening states that are used in this script.")]
    [SerializeField]
    private bool isGlobalListener = false;

    [Header("Allow losing focus during interaction (recommended)")]
    [SerializeField]
    private bool allowTemporarilyLoseFocus = true;

    private bool currentlyRegisteredModalInputHandler = false;

    private bool isWorldAnchorOwner = false;

    [Header("Rotation in Local Space or World Space?")]
    [Tooltip("Defines if rotation should be applied in local space or world space")]
    [SerializeField]
    private bool useWorldSpaceRotation = true;

    [Header("Highlighting:")]
    [Tooltip("Provides highlighting capability if the object gets focused by the user. " +
    "Note: Highlighting needs Emission to be enabled in the shader of the object!")]
    [SerializeField]
    private bool highlightingEnabled = true;

    [Tooltip("The color that is used for highlighting.")]
    [SerializeField]
    private Color highlightingColor = Color.gray;

    [Header("Debug Functionality:")]
    [Tooltip("Should a text field be used to indicate which interaction mode is currently active?")]
    [SerializeField]
    private bool useTextfieldToDebug = false;

    [Tooltip("The text field that is responsible to show which interaction mode is currently active (optional)")]
    [SerializeField]
    private Text textForInteractionMode;

    // The interaction modes to choose from for this specific object
    public enum InteractionModeBehaviour
    {
        None,
        RotateAll,
        RotateY,
        RotateX,
        RotateZ,
        Move,
        Scale,
        TapToPlace,
        TapEvent,
        ToggleEvent,
        HoldEvent
    }

    [Header("Transformation Settings:")]
    [Tooltip("Indicates how fast an object can be rotated by using hand gesture")]
    [SerializeField]
    [Range(0.01f, 10.0f)]
    private float rotationSpeed = 5f;

    [Tooltip("Controls the speed at which the object will interpolate toward the desired position in drag mode")]
    [SerializeField]
    [Range(0.01f, 1.0f)]
    private float positionLerpSpeed = 1f;

    [Tooltip("Indicates how fast an object can be scaled by using hand gesture")]
    [SerializeField]
    [Range(0.1f, 2f)]
    private float scaleSpeed = 1f;

    [Tooltip("The minimum scale of the object when scaling")]
    [SerializeField]
    private float minimumObjectSize = 0.3f;
    [Tooltip("The maximum scale of the object when scaling")]
    [SerializeField]
    private float maximumObjectSize = 1000f;

    [Tooltip("Scale by which hand movement in z is multipled to move the dragged object.")]
    public float distanceScaleZ = 2f;

    // Variables for movement mode
    private IInputSource currentInputSource = null;
    uint currentInputSourceId = 0;
    Vector3 draggingPosition;
    Quaternion draggingRotation;
    private Vector3 objRefForward;
    private Vector3 objRefUp;
    private float objRefDistance;
    private Quaternion gazeAngularOffset;
    private float handRefDistance;
    private Vector3 objRefGrabPoint;

    public enum OrientationModeEnum
    {
        Default,
        LockObjectRotation,
        OrientTowardUser,
        OrientTowardUserAndKeepUpright
    }

    [Tooltip("How the object should be oriented to the user during movement")]
    public OrientationModeEnum ObjectOrientationInMoveMode = OrientationModeEnum.Default;

    [Tooltip("Transform that will be dragged. Defaults to the object of the component.")]
    private Transform HostTransform;

    [Header("Interaction mode:")]
    [Tooltip("Defines how this gameobject should react to user input.")]
    [SerializeField]
    private InteractionModeBehaviour interactionMode;

    [Header("Event Customizations:")]
    [Tooltip("If checked the object can still react to Hold Input while another mode is active.")]
    [SerializeField]
    private bool executeHoldInputEvents = false;

    private bool currentToggleState = false;

    [Tooltip("Allows to execute custom events on Focus Enter and Focus Exit.")]
    [SerializeField]
    private bool executeFocusChangedEvents;

    [System.Serializable]
    public struct EventsTapMode
    {
        public UnityEvent eventsOnTap;
    }

    [Tooltip("An array of string keywords and UnityEvents, to be set in the Inspector.")]
    [Header("Events to be executed in TapEvent mode:")]
    [SerializeField]
    public EventsTapMode eventsTapMode;

    [System.Serializable]
    public struct EventsToggleMode
    {
        public UnityEvent eventsOnToggleStateOne;
        public UnityEvent eventsOnToggleStateTwo;
    }

    [Tooltip("Toggle event mode switches forth and back between two event queues.")]
    [Header("Events to be executed in ToggleEvent mode:")]
    [SerializeField]
    public EventsToggleMode eventsToggleMode;

    [System.Serializable]
    public struct EventsHoldMode
    {
        public UnityEvent eventsOnHoldStarted;
        public UnityEvent eventsOnHoldCompleted;
    }

    [Tooltip("An array of string keywords and UnityEvents, to be set in the Inspector.")]
    [Header("Events to be executed in HoldEvent mode:")]
    [SerializeField]
    public EventsHoldMode eventsHoldMode;

    [System.Serializable]
    public struct EventsOnFocusChanged
    {
        public UnityEvent eventsOnFocusEnter;
        public UnityEvent eventsOnFocusExit;
    }

    [Tooltip("An array of string keywords and UnityEvents, to be set in the Inspector.")]
    [Header("Events to be executed on Focus Enter or Focus Exit:")]
    [SerializeField]
    public EventsOnFocusChanged eventsOnFocusChanged;

    // Needed for placement mode to place the gameobject properly back into hierarchy after using place mode
    // if parent transform is not set the parent object on start will be used
    private Transform parentTransform;

    // needed to calculate the new position in respect to the old object position during manipulation mode
    private Vector3 originalPosition;

    // needed to calculate the new scale in respect to the old object scale during manipulation mode
    private Vector3 originalLossyScale;

    [TextArea]
    public string Disclaimer = "This component is intended solely to speed up the prototyping process of HoloLens applications. " +
        " We do not recommend using it in a final product. " +
        "No responsibility is taken for any potential damage caused by the use of this component.";

    // Use this for initialization
    void Start()
    {
        // Update the textfield that shows the current interaction mode
        // If a textfield is attached to this component then the current interaction mode is described there
        if (textForInteractionMode != null && useTextfieldToDebug)
        {
            UpdateTextDescription();
        }

        // If this object is registered as global listener, all interactions
        // will be fired even if the gameobject isn't focused
        if (isGlobalListener)
        {
            InputManager.Instance.AddGlobalListener(gameObject);
        }

        RemoveModalInputHandler(gameObject);
    }

    #region Getter And Setter Methods For Current Interaction Mode
    public InteractionModeBehaviour GetCurrentInteractionMode()
    {
        return interactionMode;
    }

    public bool GetCurrentInteractionModeIsNavigation()
    {
        if (interactionMode == InteractionModeBehaviour.RotateAll ||
        interactionMode == InteractionModeBehaviour.RotateX ||
        interactionMode == InteractionModeBehaviour.RotateY ||
        interactionMode == InteractionModeBehaviour.RotateZ ||
        interactionMode == InteractionModeBehaviour.Scale)
        {
            return true;
        }
        else { return false; }
    }

    public bool GetCurrentInteractionModeIsManipulation()
    {
        if (interactionMode == InteractionModeBehaviour.Move)
        {
            return true;
        }
        else { return false; }
    }

    public void SetRotationSpace(bool isWorldSpace)
    {
        useWorldSpaceRotation = isWorldSpace;
    }

    public void SetActiveRotateAllMode()
    {
        SetActiveNewMode(InteractionModeBehaviour.RotateAll);
    }

    public void SetActiveRotateXMode()
    {
        SetActiveNewMode(InteractionModeBehaviour.RotateX);
    }

    public void SetActiveRotateYMode()
    {
        SetActiveNewMode(InteractionModeBehaviour.RotateY);
    }

    public void SetActiveRotateZMode()
    {
        SetActiveNewMode(InteractionModeBehaviour.RotateZ);
    }

    public void SetActiveMovementMode()
    {
        SetActiveNewMode(InteractionModeBehaviour.Move);
    }

    public void SetActiveScalingMode()
    {
        SetActiveNewMode(InteractionModeBehaviour.Scale);
    }

    public void SetActivePlacementMode()
    {
        SetActiveNewMode(InteractionModeBehaviour.TapToPlace);
    }

    /// <summary>
    /// Tap event mode allows to execute a custom event if an object is clicked.
    /// The desired event can be defined in the editor.
    /// </summary>
    public void SetActiveTapEventMode()
    {
        SetActiveNewMode(InteractionModeBehaviour.TapEvent);
    }

    public void SetActiveToggleEventMode()
    {
        SetActiveNewMode(InteractionModeBehaviour.ToggleEvent);
    }

    public void SetActiveHoldEventMode()
    {
        SetActiveNewMode(InteractionModeBehaviour.HoldEvent);
    }

    public void SetActiveNextMode()
    {
        // if end of enum hasn't been reached select the next interaction mode else start from the beginning
        if ((int)interactionMode < Enum.GetValues(typeof(InteractionModeBehaviour)).Length - 1)
        {

            SetActiveNewMode(interactionMode + 1);
        }
        else
        {
            SetActiveNewMode(0);
        }

    }

    public void SetActiveNewMode(InteractionModeBehaviour iMode)
    {
        // If the previous interaction mode was placement mode put the current gameobject back into hierarchy
        if (interactionMode == InteractionModeBehaviour.TapToPlace && iMode != InteractionModeBehaviour.TapToPlace)
        {
            gameObject.transform.SetParent(parentTransform);
        }

        // Set new interaction mode
        interactionMode = iMode;

        // If a textfield is attached to this component then the current interaction mode is described there
        if (textForInteractionMode != null && useTextfieldToDebug)
        {
            UpdateTextDescription();
        }
    }
    #endregion

    #region Helper Functions For Object Highlighting and Debugging
    public void SetEmissionColorOfChildren(Color col)
    {
        // Get all MeshRenderer components from child objects
        MeshRenderer[] meshRendererInChildren = transform.GetComponentsInChildren<MeshRenderer>();
        // for each MeshRenderer get the applied materials and change its emission color
        foreach (MeshRenderer meshRendererChild in meshRendererInChildren)
        {
            Material[] mats = meshRendererChild.materials;
            foreach (Material mat in mats)
            {
                mat.SetColor("_EmissionColor", col);
            }
        }
    }

    void UpdateTextDescription()
    {
        textForInteractionMode.text = interactionMode.ToString();
    }

    void RegisterModalInputHandler(GameObject go)
    {
        // register the object temporarily as modal input handler to get all events during manipulation
        if (!isGlobalListener && !currentlyRegisteredModalInputHandler && allowTemporarilyLoseFocus)
        {
            InputManager.Instance.PushModalInputHandler(gameObject);
            currentlyRegisteredModalInputHandler = true;
        }
    }

    void RemoveModalInputHandler(GameObject go)
    {
        // remove the object as modal input handler after gesture is completed or cancelled
        if (!isGlobalListener && currentlyRegisteredModalInputHandler)
        {
            InputManager.Instance.PopModalInputHandler();
            currentlyRegisteredModalInputHandler = false;
        }
    }

    bool IsWorldAnchorOwner()
    {
        if (GetComponent<WorldAnchor>() != null)
        {
            return true;
        }
        else
        {
            return false;
        }
    }

    void AddWorldAnchorComponentIfOwner()
    {
        if (gameObject.GetComponent<WorldAnchor>() == null)
        {
            if (isWorldAnchorOwner)
            {
                gameObject.AddComponent<WorldAnchor>();
            }
        }
    }

    void RemoveWorldAnchorComponent()
    {
        isWorldAnchorOwner = IsWorldAnchorOwner();
        if (isWorldAnchorOwner)
        {
            DestroyImmediate(gameObject.GetComponent<WorldAnchor>());
        }
    }
    #endregion

    #region Focus Handling
    public void OnFocusEnter()
    {
        if (highlightingEnabled)
        {
            // Highlight the focused object by changing its emission color
            SetEmissionColorOfChildren(highlightingColor);
        }

        // if execute focus changed events is checked run them
        if (executeFocusChangedEvents)
        {
            eventsOnFocusChanged.eventsOnFocusEnter.Invoke();
        }
    }

    public void OnFocusExit()
    {

        if (highlightingEnabled)
        {
            // Set emission color back to black when focus is lost
            SetEmissionColorOfChildren(Color.black);
        }

        // if execute focus changed events is checked run them
        if (executeFocusChangedEvents)
        {
            eventsOnFocusChanged.eventsOnFocusExit.Invoke();
        }

        switch (interactionMode)
        {
            // If focus is lost while in placement mode reattach the object back to its original position
            case InteractionModeBehaviour.TapToPlace:
                if (gameObject.transform.parent == Camera.main.transform)
                {
                    // Set object back into its hierarchy
                    gameObject.transform.SetParent(parentTransform);
                    AddWorldAnchorComponentIfOwner();
                }
                break;
        }
    }
    #endregion

    #region Simple Tap Gesture
    public void OnInputClicked(InputClickedEventData eventData)
    {
        switch (interactionMode)
        {
            // If ManipulationMode has been set to Placement then attach the object to the camera until the objects gets tapped again
            case InteractionModeBehaviour.TapToPlace:
                if (gameObject.transform.parent == Camera.main.transform)
                {
                    // Set object back into its hierarchy after placement
                    gameObject.transform.SetParent(parentTransform);
                    AddWorldAnchorComponentIfOwner();
                }
                else
                {
                    // if a world anchor component is attached to the object it must be removed
                    // before transformation
                    RemoveWorldAnchorComponent();
                    parentTransform = gameObject.transform.parent;
                    gameObject.transform.SetParent(Camera.main.transform);
                }
                break;
            // If the object should fire an event it will be invoked
            case InteractionModeBehaviour.TapEvent:
                eventsTapMode.eventsOnTap.Invoke();
                break;
            case InteractionModeBehaviour.ToggleEvent:
                // switch between two toggle states
                if (!currentToggleState)
                {
                    eventsToggleMode.eventsOnToggleStateOne.Invoke();
                    currentToggleState = !currentToggleState;
                }
                else
                {
                    eventsToggleMode.eventsOnToggleStateTwo.Invoke();
                    currentToggleState = !currentToggleState;
                }
                break;
        }

    }
    #endregion

    #region Navigation Gesture Handling
    public void OnNavigationStarted(NavigationEventData eventData)
    {
        // store the original scale of the object for scaling mode
        originalLossyScale = transform.lossyScale;

        // if current interaction mode utilizes navigation then remove world anchor component
        if (GetCurrentInteractionModeIsNavigation())
        {
            RemoveWorldAnchorComponent();
            // Even if the object is no general global listener it might be helpful
            // to register it as modal input handler temporarily to provide smooth
            // interaction while a navigation gesture or manipulation gesture
            // takes place. This prevents that the object suddenly stops when
            // focus is lost temporarily.
            RegisterModalInputHandler(gameObject);
        }
    }

    /// <summary>
    /// Depending on the current interaction mode a different rotation mode will be executed
    /// </summary>
    public void OnNavigationUpdated(NavigationEventData eventData)
    {
        switch (interactionMode)
        {
            // Choose the corresponding rotation mode for the specific object
            case InteractionModeBehaviour.RotateAll:
                if (useWorldSpaceRotation)
                {
                    gameObject.transform.RotateAround(gameObject.transform.position, Camera.main.transform.up, -eventData.NormalizedOffset.x * rotationSpeed);
                    gameObject.transform.RotateAround(gameObject.transform.position, Camera.main.transform.right, eventData.NormalizedOffset.y * rotationSpeed);
                    gameObject.transform.RotateAround(gameObject.transform.position, Camera.main.transform.forward, -eventData.NormalizedOffset.z * rotationSpeed);
                }
                else
                {
                    gameObject.transform.Rotate(new Vector3(eventData.NormalizedOffset.y * rotationSpeed, -eventData.NormalizedOffset.x * rotationSpeed, -eventData.NormalizedOffset.z * rotationSpeed));
                }
                break;
            case InteractionModeBehaviour.RotateY:
                if (useWorldSpaceRotation)
                {
                    gameObject.transform.RotateAround(gameObject.transform.position, Camera.main.transform.up, -eventData.NormalizedOffset.x * rotationSpeed);
                }
                else
                {
                    gameObject.transform.Rotate(new Vector3(0, -eventData.NormalizedOffset.x * rotationSpeed, 0));
                }
                break;
            case InteractionModeBehaviour.RotateX:
                if (useWorldSpaceRotation)
                {
                    gameObject.transform.RotateAround(gameObject.transform.position, Camera.main.transform.right, -eventData.NormalizedOffset.x * rotationSpeed);
                }
                else
                {
                    gameObject.transform.Rotate(new Vector3(-eventData.NormalizedOffset.x * rotationSpeed, 0, 0));
                }
                break;
            case InteractionModeBehaviour.RotateZ:
                if (useWorldSpaceRotation)
                {
                    gameObject.transform.RotateAround(gameObject.transform.position, Camera.main.transform.forward, -eventData.NormalizedOffset.x * rotationSpeed);
                }
                else
                {
                    gameObject.transform.Rotate(new Vector3(0, 0, -eventData.NormalizedOffset.x * rotationSpeed));
                }
                break;
            // If interaction mode has been set to Scale then scale the current object by hand
            case InteractionModeBehaviour.Scale:
                if (interactionMode == InteractionModeBehaviour.Scale)
                {
                    Vector3 newLossyScale = originalLossyScale * ((scaleSpeed * eventData.NormalizedOffset.x) + 1);

                    if (newLossyScale.magnitude > gameObject.transform.localScale.magnitude)
                    {
                        gameObject.transform.localScale = newLossyScale.magnitude < maximumObjectSize ? newLossyScale : gameObject.transform.localScale;
                    }
                    else
                    {
                        gameObject.transform.localScale = newLossyScale.magnitude > minimumObjectSize ? newLossyScale : gameObject.transform.localScale;
                    }
                }
                break;
        }
    }

    public void OnNavigationCompleted(NavigationEventData eventData)
    {
        // after completion of navigation gesture add world anchor component again
        if (GetCurrentInteractionModeIsNavigation())
        {
            RemoveModalInputHandler(gameObject);
            AddWorldAnchorComponentIfOwner();
        }
    }

    public void OnNavigationCanceled(NavigationEventData eventData)
    {
        // if the execution was cancelled add world anchor component again
        if (GetCurrentInteractionModeIsNavigation())
        {
            RemoveModalInputHandler(gameObject);
            AddWorldAnchorComponentIfOwner();
        }
    }
    #endregion

    #region Manipulation Gesture Handling

    public void OnManipulationStarted(ManipulationEventData eventData)
    {
        // store the original position of the object to be able to calculate the final position
        originalPosition = transform.position;

        StartingMovement();

        if (GetCurrentInteractionModeIsManipulation())
        {
            // if current interaction mode utilizes manipulation then remove world anchor component
            RemoveWorldAnchorComponent();
            // Add the object temporarily as global listener during manipulation mode
            RegisterModalInputHandler(gameObject);
        }
    }

    /// <summary>
    /// One simple example for object movement via hand gesture
    /// </summary>
    public void OnManipulationUpdated(ManipulationEventData eventData)
    {
        // If ManipulationMode has been set to Move then move the current object by hand
        switch (interactionMode)
        {
            case InteractionModeBehaviour.Move:
                UpdateMovePosition();
                break;
        }
    }

    // movement behaviour derived from the hand draggable component which is part of the HoloToolkit
    void StartingMovement()
    {
        HostTransform = gameObject.transform;
        Vector3 gazeHitPosition = GazeManager.Instance.HitInfo.point;
        Vector3 handPosition;
        currentInputSource.TryGetPointerPosition(currentInputSourceId, out handPosition);

        Vector3 pivotPosition = GetHandPivotPosition();
        handRefDistance = Vector3.Magnitude(handPosition - pivotPosition);
        objRefDistance = Vector3.Magnitude(gazeHitPosition - pivotPosition);

        Vector3 objForward = HostTransform.forward;
        Vector3 objUp = HostTransform.up;

        // Store where the object was grabbed from
        objRefGrabPoint = Camera.main.transform.InverseTransformDirection(HostTransform.position - gazeHitPosition);

        Vector3 objDirection = Vector3.Normalize(gazeHitPosition - pivotPosition);
        Vector3 handDirection = Vector3.Normalize(handPosition - pivotPosition);

        objForward = Camera.main.transform.InverseTransformDirection(objForward);       // in camera space
        objUp = Camera.main.transform.InverseTransformDirection(objUp);                 // in camera space
        objDirection = Camera.main.transform.InverseTransformDirection(objDirection);   // in camera space
        handDirection = Camera.main.transform.InverseTransformDirection(handDirection); // in camera space

        objRefForward = objForward;
        objRefUp = objUp;

        // Store the initial offset between the hand and the object, so that we can consider it when dragging
        gazeAngularOffset = Quaternion.FromToRotation(handDirection, objDirection);
        draggingPosition = gazeHitPosition;
    }

    // movement behaviour derived from the hand draggable component which is part of the HoloToolkit
    void UpdateMovePosition()
    {
        Vector3 newHandPosition = new Vector3();
        if (currentInputSource != null)
        {
            currentInputSource.TryGetPointerPosition(currentInputSourceId, out newHandPosition);
        }

        Vector3 pivotPosition = GetHandPivotPosition();

        Vector3 newHandDirection = Vector3.Normalize(newHandPosition - pivotPosition);

        newHandDirection = Camera.main.transform.InverseTransformDirection(newHandDirection); // in camera space
        Vector3 targetDirection = Vector3.Normalize(gazeAngularOffset * newHandDirection);
        targetDirection = Camera.main.transform.TransformDirection(targetDirection); // back to world space

        float currenthandDistance = Vector3.Magnitude(newHandPosition - pivotPosition);

        float distanceRatio = currenthandDistance / handRefDistance;
        float distanceOffset = distanceRatio > 0 ? (distanceRatio - 1f) * distanceScaleZ : 0;
        float targetDistance = objRefDistance + distanceOffset;

        draggingPosition = pivotPosition + (targetDirection * targetDistance);

        if (ObjectOrientationInMoveMode == OrientationModeEnum.OrientTowardUser || ObjectOrientationInMoveMode == OrientationModeEnum.OrientTowardUserAndKeepUpright)
        {
            draggingRotation = Quaternion.LookRotation(HostTransform.position - pivotPosition);
        }
        else if (ObjectOrientationInMoveMode == OrientationModeEnum.LockObjectRotation)
        {
            draggingRotation = HostTransform.rotation;
        }
        else // RotationModeEnum.Default
        {
            Vector3 objForward = Camera.main.transform.TransformDirection(objRefForward); // in world space
            Vector3 objUp = Camera.main.transform.TransformDirection(objRefUp);   // in world space
            draggingRotation = Quaternion.LookRotation(objForward, objUp);
        }

        // Apply Final Position
        HostTransform.position = Vector3.Lerp(HostTransform.position, draggingPosition + Camera.main.transform.TransformDirection(objRefGrabPoint), positionLerpSpeed);
        // Apply Final Rotation
        HostTransform.rotation = Quaternion.Lerp(HostTransform.rotation, draggingRotation, rotationSpeed);

        if (ObjectOrientationInMoveMode == OrientationModeEnum.OrientTowardUserAndKeepUpright)
        {
            Quaternion upRotation = Quaternion.FromToRotation(HostTransform.up, Vector3.up);
            HostTransform.rotation = upRotation * HostTransform.rotation;
        }
    }

    /// <summary>
    /// Gets the pivot position for the hand, which is approximated to the base of the neck.
    /// </summary>
    /// <returns>Pivot position for the hand.</returns>
    private Vector3 GetHandPivotPosition()
    {
        Vector3 pivot = Camera.main.transform.position + new Vector3(0, -0.2f, 0) - Camera.main.transform.forward * 0.2f; // a bit lower and behind
        return pivot;
    }

    public void OnManipulationCompleted(ManipulationEventData eventData)
    {
        // after completion add world anchor component again
        // and unregister as global listener
        if (GetCurrentInteractionModeIsManipulation())
        {
            RemoveModalInputHandler(gameObject);
            AddWorldAnchorComponentIfOwner();
        }
    }

    public void OnManipulationCanceled(ManipulationEventData eventData)
    {
        // if execution was cancelled add world anchor component again
        if (GetCurrentInteractionModeIsManipulation())
        {
            RemoveModalInputHandler(gameObject);
            AddWorldAnchorComponentIfOwner();
        }
    }
    #endregion

    #region Hold Gesture Handling
    public void OnHoldStarted(HoldEventData eventData)
    {
        switch (interactionMode)
        {
            case InteractionModeBehaviour.HoldEvent:
                eventsHoldMode.eventsOnHoldStarted.Invoke();
                break;
            // if "react to Hold and Tap" is checked, Hold events will
            // still be executed if object is in Tap Mode
            default:
                if (executeHoldInputEvents)
                {
                    eventsHoldMode.eventsOnHoldStarted.Invoke();
                }
                break;
        }

    }

    public void OnHoldCompleted(HoldEventData eventData)
    {
        // If the object should fire an event it will be invoked
        switch (interactionMode)
        {
            case InteractionModeBehaviour.HoldEvent:
                eventsHoldMode.eventsOnHoldCompleted.Invoke();
                break;
            // if "reactToHoldInput" is checked, Hold events will
            // still be executed if while another mode is active
            default:
                if (executeHoldInputEvents)
                {
                    eventsHoldMode.eventsOnHoldCompleted.Invoke();
                }
                break;
        }
    }

    public void OnHoldCanceled(HoldEventData eventData)
    {

    }
    #endregion

    #region Input Up And Down State
    public void OnInputDown(InputEventData eventData)
    {
        // get input source to be able to get hand position etc.
        currentInputSource = eventData.InputSource;
        currentInputSourceId = eventData.SourceId;
    }

    public void OnInputUp(InputEventData eventData)
    {

    }
    #endregion

}
