// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Microsoft.MixedReality.Toolkit.Utilities;
using System;
using System.Collections.Generic;
using Unity.Profiling;
using UnityEngine;
using UnityEngine.EventSystems;

namespace Microsoft.MixedReality.Toolkit.Input
{
    /// <summary>
    /// The Mixed Reality Toolkit's specific implementation of the <see cref="Microsoft.MixedReality.Toolkit.Input.IMixedRealityInputSystem"/>
    /// </summary>
    [HelpURL("https://docs.microsoft.com/windows/mixed-reality/mrtk-unity/features/input/overview")]
    public class MixedRealityInputSystem : BaseDataProviderAccessCoreSystem, IMixedRealityInputSystem, IMixedRealityCapabilityCheck
    {
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="registrar">The <see cref="IMixedRealityServiceRegistrar"/> instance that loaded the service.</param>
        /// <param name="profile">The configuration profile for the service.</param>
        [Obsolete("This constructor is obsolete (registrar parameter is no longer required) and will be removed in a future version of the Microsoft Mixed Reality Toolkit.")]
        public MixedRealityInputSystem(
            IMixedRealityServiceRegistrar registrar,
            MixedRealityInputSystemProfile profile) : this(profile)
        {
            Registrar = registrar;
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="profile">The configuration profile for the service.</param>
        public MixedRealityInputSystem(
            MixedRealityInputSystemProfile profile) : base(profile)
        { }

        private static readonly ProfilerMarker ExecuteHierarchyPerfMarker = new ProfilerMarker("[MRTK] MixedRealityInputSystem - Calling EventSystems.ExecuteEvents.ExecuteHierarchy");

        /// <inheritdoc/>
        public override string Name { get; protected set; } = "Mixed Reality Input System";

        /// <inheritdoc />
        public event Action InputEnabled;

        /// <inheritdoc />
        public event Action InputDisabled;

        /// <inheritdoc />
        public HashSet<IMixedRealityInputSource> DetectedInputSources { get; } = new HashSet<IMixedRealityInputSource>();

        /// <inheritdoc />
        public HashSet<IMixedRealityController> DetectedControllers { get; } = new HashSet<IMixedRealityController>();


        private MixedRealityInputSystemProfile inputSystemProfile = null;

        /// <inheritdoc/>
        public MixedRealityInputSystemProfile InputSystemProfile
        {
            get
            {
                if (inputSystemProfile == null)
                {
                    inputSystemProfile = ConfigurationProfile as MixedRealityInputSystemProfile;
                }
                return inputSystemProfile;
            }
        }

        /// <inheritdoc />
        public IMixedRealityFocusProvider FocusProvider => focusProvider != null ? focusProvider : focusProvider = CoreServices.FocusProvider;
        private IMixedRealityFocusProvider focusProvider = null;

        /// <inheritdoc />
        public IMixedRealityRaycastProvider RaycastProvider => raycastProvider != null ? raycastProvider : raycastProvider = CoreServices.RaycastProvider;
        private IMixedRealityRaycastProvider raycastProvider = null;

        /// <inheritdoc />
        public IMixedRealityGazeProvider GazeProvider { get; private set; }

        /// <inheritdoc />
        public IMixedRealityEyeGazeProvider EyeGazeProvider { get; private set; }

        private readonly Stack<GameObject> modalInputStack = new Stack<GameObject>();
        private readonly Stack<GameObject> fallbackInputStack = new Stack<GameObject>();

        /// <inheritdoc />
        public bool IsInputEnabled => disabledRefCount <= 0;

        private int disabledRefCount;
        private bool isInputModuleAdded = false;

        private SourceStateEventData sourceStateEventData;
        private SourcePoseEventData<TrackingState> sourceTrackingEventData;
        private SourcePoseEventData<Vector2> sourceVector2EventData;
        private SourcePoseEventData<Vector3> sourcePositionEventData;
        private SourcePoseEventData<Quaternion> sourceRotationEventData;
        private SourcePoseEventData<MixedRealityPose> sourcePoseEventData;

        private FocusEventData focusEventData;

        private InputEventData inputEventData;
        private MixedRealityPointerEventData pointerEventData;

        private InputEventData<float> floatInputEventData;
        private InputEventData<Vector2> vector2InputEventData;
        private InputEventData<Vector3> positionInputEventData;
        private InputEventData<Quaternion> rotationInputEventData;
        private InputEventData<MixedRealityPose> poseInputEventData;
        private InputEventData<IDictionary<TrackedHandJoint, MixedRealityPose>> jointPoseInputEventData;
        private InputEventData<HandMeshInfo> handMeshInputEventData;

        private SpeechEventData speechEventData;
        private DictationEventData dictationEventData;

        private HandTrackingInputEventData handTrackingInputEventData;

        private MixedRealityInputActionRulesProfile CurrentInputActionRulesProfile { get; set; }

        private bool inputModuleChecked = false;
        private MixedRealityInputModule inputModule;

        #region IMixedRealityCapabilityCheck Implementation

        /// <inheritdoc />
        public bool CheckCapability(MixedRealityCapability capability)
        {
            foreach (var deviceManager in GetDataProviders<IMixedRealityInputDeviceManager>())
            {
                IMixedRealityCapabilityCheck capabilityChecker = deviceManager as IMixedRealityCapabilityCheck;

                // If one of the running data providers supports the requested capability, 
                // the application has the needed support to leverage the desired functionality.
                if (capabilityChecker?.CheckCapability(capability) == true)
                {
                    return true;
                }
            }

            // Check GazeProvider directly since not populated in data provider list but life-cycle is managed by InputSystem
            var gazeProvider_CapabilityCheck = GazeProvider as IMixedRealityCapabilityCheck;
            if (gazeProvider_CapabilityCheck?.CheckCapability(capability) == true)
            {
                return true;
            }

            return false;
        }

        #endregion IMixedRealityCapabilityCheck Implementation

        #region IMixedRealityService Implementation

        /// <inheritdoc />
        /// <remarks>
        /// Input system is critical, so should be processed before all other managers
        /// </remarks>
        public override uint Priority => 1;

        /// <inheritdoc />
        public override void Initialize()
        {
            MixedRealityInputSystemProfile profile = ConfigurationProfile as MixedRealityInputSystemProfile;
            if (profile == null)
            {
                Debug.LogError("The Input system is missing the required Input System Profile!");
                return;
            }

            BaseInputModule[] inputModules = UnityEngine.Object.FindObjectsOfType<BaseInputModule>();

            if (inputModules.Length == 0)
            {
                DebugUtilities.LogVerbose("MixedRealityInputModule added to main camera");
                // There is no input module attached to the camera, add one.
                inputModule = CameraCache.Main.gameObject.AddComponent<MixedRealityInputModule>();
                isInputModuleAdded = true;
            }
            else if ((inputModules.Length == 1) && (inputModules[0] is MixedRealityInputModule))
            {
                inputModule = inputModules[0] as MixedRealityInputModule;
            }
            else
            {
                Debug.LogError($"For Mixed Reality Toolkit input to work properly, please remove your other input module(s) and add a {typeof(MixedRealityInputModule).Name} to your main camera.", inputModules[0]);
            }

            if (InputSystemProfile == null)
            {
                Debug.LogError("The Input system is missing the required Input System Profile!");
                return;
            }

            if (profile.InputActionRulesProfile != null)
            {
                CurrentInputActionRulesProfile = profile.InputActionRulesProfile;
            }
            else
            {
                Debug.LogError("The Input system is missing the required Input Action Rules Profile!");
                return;
            }

            if (profile.PointerProfile != null)
            {
                InstantiateGazeProvider(profile.PointerProfile);
            }
            else
            {
                Debug.LogError("The Input system is missing the required Pointer Profile!");
                return;
            }

            sourceStateEventData = new SourceStateEventData(EventSystem.current);

            sourceTrackingEventData = new SourcePoseEventData<TrackingState>(EventSystem.current);
            sourceVector2EventData = new SourcePoseEventData<Vector2>(EventSystem.current);
            sourcePositionEventData = new SourcePoseEventData<Vector3>(EventSystem.current);
            sourceRotationEventData = new SourcePoseEventData<Quaternion>(EventSystem.current);
            sourcePoseEventData = new SourcePoseEventData<MixedRealityPose>(EventSystem.current);

            focusEventData = new FocusEventData(EventSystem.current);

            inputEventData = new InputEventData(EventSystem.current);
            pointerEventData = new MixedRealityPointerEventData(EventSystem.current);

            floatInputEventData = new InputEventData<float>(EventSystem.current);
            vector2InputEventData = new InputEventData<Vector2>(EventSystem.current);
            positionInputEventData = new InputEventData<Vector3>(EventSystem.current);
            rotationInputEventData = new InputEventData<Quaternion>(EventSystem.current);
            poseInputEventData = new InputEventData<MixedRealityPose>(EventSystem.current);
            jointPoseInputEventData = new InputEventData<IDictionary<TrackedHandJoint, MixedRealityPose>>(EventSystem.current);
            handMeshInputEventData = new InputEventData<HandMeshInfo>(EventSystem.current);

            speechEventData = new SpeechEventData(EventSystem.current);
            dictationEventData = new DictationEventData(EventSystem.current);

            handTrackingInputEventData = new HandTrackingInputEventData(EventSystem.current);

            CreateDataProviders();

            // Call the base after initialization to ensure any early exits do not
            // artificially declare the service as initialized.
            base.Initialize();
        }

        /// <inheritdoc />
        public override void Enable()
        {
            CreateDataProviders();

            // Ensure data providers are enabled (performed by the base class)
            base.Enable();

            InputEnabled?.Invoke();
        }

        public override void LateUpdate()
        {
            // Check whether manual initialization of input module is needed.
            // The check is only required once after input system is created.
            if (!isInputModuleAdded && !inputModuleChecked)
            {
                if (inputModule.ManualInitializationRequired)
                {
                    inputModule.Initialize();
                }
                inputModuleChecked = true;
            }
            base.LateUpdate();
        }

        private void CreateDataProviders()
        {
            MixedRealityInputSystemProfile profile = ConfigurationProfile as MixedRealityInputSystemProfile;

            // If the system gets disabled, the gaze provider is destroyed.
            // Ensure that it gets recreated on when re-enabled.
            if (GazeProvider == null && profile != null)
            {
                InstantiateGazeProvider(profile.PointerProfile);
            }

            if ((GetDataProviders().Count == 0) && (profile != null))
            {
                // Register the input device managers.
                for (int i = 0; i < profile.DataProviderConfigurations.Length; i++)
                {
                    MixedRealityInputDataProviderConfiguration configuration = profile.DataProviderConfigurations[i];
                    object[] args = { this, configuration.ComponentName, configuration.Priority, configuration.Profile };

                    DebugUtilities.LogVerboseFormat(
                        "Attempting to register input system data provider {0}, {1}, {2}",
                        configuration.ComponentType.Type,
                        configuration.ComponentName,
                        configuration.RuntimePlatform);

                    RegisterDataProvider<IMixedRealityInputDeviceManager>(
                        configuration.ComponentType.Type,
                        configuration.ComponentName,
                        configuration.RuntimePlatform,
                        args);
                }
            }
        }

        private void InstantiateGazeProvider(MixedRealityPointerProfile pointerProfile)
        {
            if (pointerProfile != null && pointerProfile.GazeProviderType?.Type != null && CameraCache.Main != null)
            {
                IMixedRealityGazeProvider existingGazeProvider = CameraCache.Main.gameObject.GetComponent<IMixedRealityGazeProvider>();
                if (existingGazeProvider != null && existingGazeProvider.GetType() != pointerProfile.GazeProviderType.Type)
                {
                    UnityEngine.Object.DestroyImmediate((UnityEngine.Object)existingGazeProvider);
                }
                GazeProvider = CameraCache.Main.gameObject.EnsureComponent(pointerProfile.GazeProviderType.Type) as IMixedRealityGazeProvider;
                GazeProvider.GazeCursorPrefab = pointerProfile.GazeCursorPrefab;
                DebugUtilities.LogVerboseFormat("Initialized a gaze provider of type {0}", pointerProfile.GazeProviderType.Type);
                // Current default implementation implements both provider types in one concrete class.
                EyeGazeProvider = GazeProvider as IMixedRealityEyeGazeProvider;
                if (EyeGazeProvider != null)
                {
                    EyeGazeProvider.IsEyeTrackingEnabled = pointerProfile.IsEyeTrackingEnabled;
                    DebugUtilities.LogVerboseFormat("Gaze Provider supports IMixedRealityEyeGazeProvider, IsEyeTrackingEnabled set to {0}", EyeGazeProvider.IsEyeTrackingEnabled);
                }

                if (GazeProvider is IMixedRealityGazeProviderHeadOverride gazeProviderHeadOverride)
                {
                    gazeProviderHeadOverride.UseHeadGazeOverride = pointerProfile.UseHeadGazeOverride;
                    DebugUtilities.LogVerboseFormat("Gaze Provider supports IMixedRealityGazeProviderHeadOverride, UseHeadGazeOverride set to {0}", gazeProviderHeadOverride.UseHeadGazeOverride);
                }
            }
            else
            {
                Debug.LogError("The input system is missing the required GazeProviderType!");
            }
        }

        /// <inheritdoc />
        public override void Reset()
        {
            base.Reset();
            Disable();
            Initialize();
            Enable();
        }

        /// <inheritdoc />
        public override void Disable()
        {
            base.Disable();

            // Input System adds a gaze provider component on the main camera, which needs to be removed when the input system is disabled/removed.
            // Otherwise the component would keep references to dead objects.
            // Unity's way to remove component is to destroy it.
            if (GazeProvider != null)
            {
                if (Application.isPlaying)
                {
                    GazeProvider.GazePointer.BaseCursor.Destroy();
                    DebugUtilities.LogVerbose("Application was playing, destroyed the gaze pointer's BaseCursor");
                }

                UnityObjectExtensions.DestroyObject(GazeProvider as Component);

                GazeProvider = null;
                DebugUtilities.LogVerbose("Destroyed the GazeProvider in MixedRealityInputSystem");
            }

            foreach (var provider in GetDataProviders<IMixedRealityInputDeviceManager>())
            {
                if (provider != null)
                {
                    DebugUtilities.LogVerboseFormat("Unregistering input data provider {0}", provider);
                    UnregisterDataProvider<IMixedRealityInputDeviceManager>(provider);
                }
            }

            InputDisabled?.Invoke();
        }

        public override void Destroy()
        {
            if (isInputModuleAdded)
            {
                if (inputModule)
                {
                    if (Application.isPlaying)
                    {
                        inputModule.DeactivateModule();
                    }

                    UnityObjectExtensions.DestroyObject(inputModule);
                }
            }
            // If the MRTK profile is being switched and there is an input module in the scene in the beginning
            else if (Application.isPlaying && inputModule != null)
            {
                inputModule.Suspend();
            }

            inputModule = null;
            base.Destroy();
        }
        #endregion IMixedRealityService Implementation

        #region IMixedRealityDataProviderAccess Implementation

        /// <inheritdoc />
        public override IReadOnlyList<T> GetDataProviders<T>()
        {
            if (!typeof(IMixedRealityInputDeviceManager).IsAssignableFrom(typeof(T)))
            {
                return null;
            }

            return base.GetDataProviders<T>();
        }

        /// <inheritdoc />
        public override T GetDataProvider<T>(string name = null)
        {
            if (!typeof(IMixedRealityInputDeviceManager).IsAssignableFrom(typeof(T)))
            {
                return default(T);
            }

            return base.GetDataProvider<T>(name);
        }

        #endregion IMixedRealityDataProviderAccess Implementation

        #region IMixedRealityEventSystem Implementation

        private static readonly ProfilerMarker HandleEventPerfMarker = new ProfilerMarker("[MRTK] MixedRealityInputSystem.HandleEvent");

        /// <inheritdoc />
        public override void HandleEvent<T>(BaseEventData eventData, ExecuteEvents.EventFunction<T> eventHandler)
        {
            using (HandleEventPerfMarker.Auto())
            {
                if (disabledRefCount > 0)
                {
                    return;
                }

                Debug.Assert(eventData != null);
                Debug.Assert(!(eventData is MixedRealityPointerEventData), "HandleEvent called with a pointer event. All events raised by pointer should call HandlePointerEvent");

                var baseInputEventData = ExecuteEvents.ValidateEventData<BaseInputEventData>(eventData);
                DispatchEventToGlobalListeners(baseInputEventData, eventHandler);

                if (baseInputEventData.used)
                {
                    // All global listeners get a chance to see the event,
                    // but if any of them marked it used,
                    // we stop the event from going any further.
                    return;
                }

                if (baseInputEventData.InputSource.Pointers == null) { Debug.LogError($"InputSource {baseInputEventData.InputSource.SourceName} doesn't have any registered pointers! Input Sources without pointers should use the GazeProvider's pointer as a default fallback."); }

                var modalEventHandled = false;
                // Get the focused object for each pointer of the event source
                for (int i = 0; i < baseInputEventData.InputSource.Pointers.Length && !baseInputEventData.used; i++)
                {
                    modalEventHandled = DispatchEventToObjectFocusedByPointer(baseInputEventData.InputSource.Pointers[i], baseInputEventData, modalEventHandled, eventHandler);
                }

                if (!baseInputEventData.used)
                {
                    DispatchEventToFallbackHandlers(baseInputEventData, eventHandler);
                }
            }
        }

        private static readonly ProfilerMarker HandleFocusChangedEventsPerfMarker = new ProfilerMarker("[MRTK] MixedRealityInputSystem.HandleFocusChangedEvents");

        /// <summary>
        /// Handles focus changed events
        /// We send all focus events to all global listeners and the actual focus change receivers. the use flag is completely ignored to avoid any interception.
        /// </summary>
        private void HandleFocusChangedEvents(FocusEventData focusEventData, ExecuteEvents.EventFunction<IMixedRealityFocusChangedHandler> eventHandler)
        {
            using (HandleFocusChangedEventsPerfMarker.Auto())
            {
                Debug.Assert(focusEventData != null);

                DispatchEventToGlobalListeners(focusEventData, eventHandler);

                // Raise Focus Events on the old and new focused objects.
                if (focusEventData.OldFocusedObject != null)
                {
                    using (ExecuteHierarchyPerfMarker.Auto())
                    {
                        ExecuteEvents.ExecuteHierarchy(focusEventData.OldFocusedObject, focusEventData, eventHandler);
                    }
                }

                if (focusEventData.NewFocusedObject != null)
                {
                    using (ExecuteHierarchyPerfMarker.Auto())
                    {
                        ExecuteEvents.ExecuteHierarchy(focusEventData.NewFocusedObject, focusEventData, eventHandler);
                    }
                }

                // Raise Focus Events on the pointers cursor if it has one.
                if (focusEventData.Pointer != null && focusEventData.Pointer.BaseCursor != null)
                {
                    try
                    {
                        using (ExecuteHierarchyPerfMarker.Auto())
                        {
                            // When shutting down a game, we can sometime get old references to game objects that have been cleaned up.
                            // We'll ignore when this happens.
                            ExecuteEvents.ExecuteHierarchy(focusEventData.Pointer.BaseCursor.GameObjectReference, focusEventData, eventHandler);
                        }
                    }
                    catch (Exception)
                    {
                        // ignored.
                    }
                }
            }
        }

        private static readonly ProfilerMarker HandleFocusEventPerfMarker = new ProfilerMarker("[MRTK] MixedRealityInputSystem.HandleFocusEvent");

        /// <summary>
        /// Handles focus enter and exit
        /// We send the focus event to all global listeners and the actual focus change receiver. the use flag is completely ignored to avoid any interception.
        /// </summary>
        private void HandleFocusEvent(GameObject eventTarget, FocusEventData focusEventData, ExecuteEvents.EventFunction<IMixedRealityFocusHandler> eventHandler)
        {
            using (HandleFocusEventPerfMarker.Auto())
            {
                Debug.Assert(focusEventData != null);

                DispatchEventToGlobalListeners(focusEventData, eventHandler);

                using (ExecuteHierarchyPerfMarker.Auto())
                {
                    ExecuteEvents.ExecuteHierarchy(eventTarget, focusEventData, eventHandler);
                }
            }
        }

        private static readonly ProfilerMarker HandlePointerEventPerfMarker = new ProfilerMarker("[MRTK] MixedRealityInputSystem.HandlePointerEvent");

        /// <summary>
        /// Handles a pointer event
        /// Assumption: We only send pointer events to the objects that pointers are focusing, except for global event listeners (which listen to everything)
        /// In contract, all other events get sent to all other pointers attached to a given input source
        /// </summary>
        private void HandlePointerEvent<T>(BaseEventData eventData, ExecuteEvents.EventFunction<T> eventHandler) where T : IMixedRealityPointerHandler
        {
            using (HandlePointerEventPerfMarker.Auto())
            {
                if (disabledRefCount > 0)
                {
                    return;
                }

                Debug.Assert(eventData != null);
                var baseInputEventData = ExecuteEvents.ValidateEventData<BaseInputEventData>(eventData);
                DispatchEventToGlobalListeners(baseInputEventData, eventHandler);

                if (baseInputEventData.used)
                {
                    // All global listeners get a chance to see the event,
                    // but if any of them marked it used,
                    // we stop the event from going any further.
                    return;
                }

                Debug.Assert(pointerEventData.Pointer != null, "Trying to dispatch event on pointer but pointerEventData is null");

                DispatchEventToObjectFocusedByPointer(pointerEventData.Pointer, baseInputEventData, false, eventHandler);

                if (!baseInputEventData.used)
                {
                    DispatchEventToFallbackHandlers(baseInputEventData, eventHandler);
                }
            }
        }

        private static readonly ProfilerMarker DispatchEventToGlobalListenersPerfMarker = new ProfilerMarker("[MRTK] MixedRealityInputSystem.DispatchEventToGlobalListeners");

        /// <summary>
        /// Dispatch an input event to all global event listeners
        /// Return true if the event has been handled by a global event listener
        /// </summary>
        private void DispatchEventToGlobalListeners<T>(BaseInputEventData baseInputEventData, ExecuteEvents.EventFunction<T> eventHandler) where T : IEventSystemHandler
        {
            using (DispatchEventToGlobalListenersPerfMarker.Auto())
            {
                Debug.Assert(baseInputEventData != null);
                Debug.Assert(!baseInputEventData.used);
                if (baseInputEventData.InputSource == null) { Debug.Assert(baseInputEventData.InputSource != null, $"Failed to find an input source for {baseInputEventData}"); }

                // Send the event to global listeners
                base.HandleEvent(baseInputEventData, eventHandler);
            }
        }

        /// <summary>
        /// Dispatch a focus event to all global event listeners
        /// </summary>
        private void DispatchEventToGlobalListeners<T>(FocusEventData focusEventData, ExecuteEvents.EventFunction<T> eventHandler) where T : IEventSystemHandler
        {
            using (DispatchEventToGlobalListenersPerfMarker.Auto())
            {
                Debug.Assert(focusEventData != null);
                Debug.Assert(!focusEventData.used);

                // Send the event to global listeners
                base.HandleEvent(focusEventData, eventHandler);
            }
        }

        private static readonly ProfilerMarker DispatchEventToFallbackHandlersPerfMarker = new ProfilerMarker("[MRTK] MixedRealityInputSystem.DispatchEventToFallbackHandlers");

        private void DispatchEventToFallbackHandlers<T>(BaseInputEventData baseInputEventData, ExecuteEvents.EventFunction<T> eventHandler) where T : IEventSystemHandler
        {
            using (DispatchEventToFallbackHandlersPerfMarker.Auto())
            {
                // If event was not handled by the focused object, pass it on to any fallback handlers
                if (!baseInputEventData.used && fallbackInputStack.Count > 0)
                {
                    GameObject fallbackInput = fallbackInputStack.Peek();
                    using (ExecuteHierarchyPerfMarker.Auto())
                    {
                        ExecuteEvents.ExecuteHierarchy(fallbackInput, baseInputEventData, eventHandler);
                    }
                }
            }
        }

        private static readonly ProfilerMarker DispatchEventToObjectFocusedPerfMarker = new ProfilerMarker("[MRTK] MixedRealityInputSystem.DispatchEventToObjectFocusedByPointer");
        private static readonly ProfilerMarker DispatchModalEventPerfMarker = new ProfilerMarker("[MRTK] MixedRealityInputSystem.DispatchEventToObjectFocusedByPointer - Modal event handling");

        /// <summary>
        /// Dispatch an input event to the object focused by the given IMixedRealityPointer.
        /// If a modal dialog is active, dispatch the pointer event to that modal dialog
        /// Returns true if the event was handled by a modal handler
        /// </summary>
        private bool DispatchEventToObjectFocusedByPointer<T>(IMixedRealityPointer mixedRealityPointer, BaseInputEventData baseInputEventData,
            bool modalEventHandled, ExecuteEvents.EventFunction<T> eventHandler) where T : IEventSystemHandler
        {
            using (DispatchEventToObjectFocusedPerfMarker.Auto())
            {
                GameObject focusedObject = FocusProvider?.GetFocusedObject(mixedRealityPointer);

                using (DispatchModalEventPerfMarker.Auto())
                {
                    // Handle modal input if one exists
                    if (modalInputStack.Count > 0 && !modalEventHandled)
                    {
                        GameObject modalInput = modalInputStack.Peek();

                        if (modalInput != null)
                        {
                            // If there is a focused object in the hierarchy of the modal handler, start the event bubble there
                            if (focusedObject != null && focusedObject.transform.IsChildOf(modalInput.transform))
                            {
                                using (ExecuteHierarchyPerfMarker.Auto())
                                {
                                    if (ExecuteEvents.ExecuteHierarchy(focusedObject, baseInputEventData, eventHandler) && baseInputEventData.used)
                                    {
                                        return true;
                                    }
                                }
                            }
                            // Otherwise, just invoke the event on the modal handler itself
                            else
                            {
                                using (ExecuteHierarchyPerfMarker.Auto())
                                {
                                    if (ExecuteEvents.ExecuteHierarchy(modalInput, baseInputEventData, eventHandler) && baseInputEventData.used)
                                    {
                                        return true;
                                    }
                                }
                            }
                        }
                        else
                        {
                            Debug.LogError("ModalInput GameObject reference was null!\nDid this GameObject get destroyed?");
                        }
                    }
                }

                // If event was not handled by modal, pass it on to the current focused object
                if (focusedObject != null)
                {
                    using (ExecuteHierarchyPerfMarker.Auto())
                    {
                        ExecuteEvents.ExecuteHierarchy(focusedObject, baseInputEventData, eventHandler);
                    }
                }
                return modalEventHandled;
            }
        }

        /// <summary>
        /// Register a <see href="https://docs.unity3d.com/ScriptReference/GameObject.html">GameObject</see> to listen to events that will receive all input events, regardless
        /// of which other <see href="https://docs.unity3d.com/ScriptReference/GameObject.html">GameObject</see>s might have handled the event beforehand.
        /// </summary>
        /// <remarks>Useful for listening to events when the <see href="https://docs.unity3d.com/ScriptReference/GameObject.html">GameObject</see> is currently not being raycasted against by the <see cref="FocusProvider"/>.</remarks>
        /// <param name="listener">Listener to add.</param>
        public override void Register(GameObject listener)
        {
            base.Register(listener);
        }

        /// <summary>
        /// Unregister a <see href="https://docs.unity3d.com/ScriptReference/GameObject.html">GameObject</see> from listening to input events.
        /// </summary>
        public override void Unregister(GameObject listener)
        {
            base.Unregister(listener);
        }

        #endregion IMixedRealityEventSystem Implementation

        #region Input Disabled Options

        /// <summary>
        /// Push a disabled input state onto the input manager.
        /// While input is disabled no events will be sent out and the cursor displays
        /// a waiting animation.
        /// </summary>
        public void PushInputDisable()
        {
            ++disabledRefCount;

            if (disabledRefCount == 1)
            {
                InputDisabled?.Invoke();

                if (GazeProvider != null)
                {
                    GazeProvider.Enabled = false;
                }
            }
        }

        /// <summary>
        /// Pop disabled input state. When the last disabled state is 
        /// popped off the stack input will be re-enabled.
        /// </summary>
        public void PopInputDisable()
        {
            --disabledRefCount;
            Debug.Assert(disabledRefCount >= 0, "Tried to pop more input disable than the amount pushed.");

            if (disabledRefCount == 0)
            {
                InputEnabled?.Invoke();

                if (GazeProvider != null)
                {
                    GazeProvider.Enabled = true;
                }
            }
        }

        /// <summary>
        /// Clear the input disable stack, which will immediately re-enable input.
        /// </summary>
        public void ClearInputDisableStack()
        {
            bool wasInputDisabled = disabledRefCount > 0;
            disabledRefCount = 0;

            if (wasInputDisabled)
            {
                InputEnabled?.Invoke();

                if (GazeProvider != null)
                {
                    GazeProvider.Enabled = true;
                }
            }
        }

        #endregion Input Disabled Options

        #region Modal Input Options

        /// <summary>
        /// Push a game object into the modal input stack. Any input handlers
        /// on the game object are given priority to input events before any focused objects.
        /// </summary>
        /// <param name="inputHandler">The input handler to push</param>
        public void PushModalInputHandler(GameObject inputHandler)
        {
            modalInputStack.Push(inputHandler);
        }

        /// <summary>
        /// Remove the last game object from the modal input stack.
        /// </summary>
        public void PopModalInputHandler()
        {
            if (modalInputStack.Count > 0)
            {
                modalInputStack.Pop();

            }
        }

        /// <summary>
        /// Clear all modal input handlers off the stack.
        /// </summary>
        public void ClearModalInputStack()
        {
            modalInputStack.Clear();
        }

        #endregion Modal Input Options

        #region Fallback Input Handler Options

        /// <summary>
        /// Push a game object into the fallback input stack. Any input handlers on
        /// the game object are given input events when no modal or focused objects consume the event.
        /// </summary>
        /// <param name="inputHandler">The input handler to push</param>
        public void PushFallbackInputHandler(GameObject inputHandler)
        {
            fallbackInputStack.Push(inputHandler);
        }

        /// <summary>
        /// Remove the last game object from the fallback input stack.
        /// </summary>
        public void PopFallbackInputHandler()
        {
            fallbackInputStack.Pop();
        }

        /// <summary>
        /// Clear all fallback input handlers off the stack.
        /// </summary>
        public void ClearFallbackInputStack()
        {
            fallbackInputStack.Clear();
        }

        #endregion Fallback Input Handler Options

        #region Input Events

        #region Input Source Events

        /// <inheritdoc />
        public uint GenerateNewSourceId()
        {
            var newId = (uint)UnityEngine.Random.Range(1, int.MaxValue);

            foreach (var inputSource in DetectedInputSources)
            {
                if (inputSource.SourceId == newId)
                {
                    return GenerateNewSourceId();
                }
            }

            return newId;
        }

        /// <inheritdoc />
        public IMixedRealityInputSource RequestNewGenericInputSource(string name, IMixedRealityPointer[] pointers = null, InputSourceType sourceType = InputSourceType.Other)
        {
            return new BaseGenericInputSource(name, pointers, sourceType);
        }

        /// <inheritdoc />
        public BaseGlobalInputSource RequestNewGlobalInputSource(string name, IMixedRealityFocusProvider focusProvider = null, InputSourceType sourceType = InputSourceType.Other)
        {
            var inputSourceFocusProvider = focusProvider.IsNull() ? FocusProvider : focusProvider;
            return new BaseGlobalInputSource(name, inputSourceFocusProvider, sourceType);
        }


        #region Input Source State Events

        private static readonly ProfilerMarker RaiseSourceDetectedPerfMarker = new ProfilerMarker("[MRTK] MixedRealityInputSystem.RaiseSourceDetected");

        /// <inheritdoc />
        public void RaiseSourceDetected(IMixedRealityInputSource source, IMixedRealityController controller = null)
        {
            using (RaiseSourceDetectedPerfMarker.Auto())
            {
                if (DetectedInputSources.Contains(source))
                {
                    Debug.LogWarning($"[MRTK Issue] {source.SourceName} has already been registered with the Input Manager!");
                    return;
                }

                // Create input event
                sourceStateEventData.Initialize(source, controller);

                DetectedInputSources.Add(source);

                if (controller != null)
                {
                    DetectedControllers.Add(controller);
                }

                DebugUtilities.LogVerboseFormat("RaiseSourceDetected: Source ID: {0}, Source Type: {1}", source.SourceId, source.SourceType);

                FocusProvider?.OnSourceDetected(sourceStateEventData);

                // Pass handler through HandleEvent to perform modal/fallback logic
                HandleEvent(sourceStateEventData, OnSourceDetectedEventHandler);
            }
        }

        internal static readonly ExecuteEvents.EventFunction<IMixedRealitySourceStateHandler> OnSourceDetectedEventHandler =
            delegate (IMixedRealitySourceStateHandler handler, BaseEventData eventData)
            {
                var casted = ExecuteEvents.ValidateEventData<SourceStateEventData>(eventData);
                handler.OnSourceDetected(casted);
            };

        private static readonly ProfilerMarker RaiseSourceLostPerfMarker = new ProfilerMarker("[MRTK] MixedRealityInputSystem.RaiseSourceLost");

        /// <inheritdoc />
        public void RaiseSourceLost(IMixedRealityInputSource source, IMixedRealityController controller = null)
        {
            using (RaiseSourceLostPerfMarker.Auto())
            {
                if (!DetectedInputSources.Contains(source))
                {
                    Debug.LogWarning($"[MRTK Issue] {source.SourceName} was never registered with the Input Manager!");
                    return;
                }

                // Create input event
                sourceStateEventData.Initialize(source, controller);

                DetectedInputSources.Remove(source);

                DebugUtilities.LogVerboseFormat("RaiseSourceLost: Source ID: {0}, Source Type: {1}", source.SourceId, source.SourceType);

                if (controller != null)
                {
                    DetectedControllers.Remove(controller);
                }

                // Pass handler through HandleEvent to perform modal/fallback logic
                // Events have to be handled before FocusProvider.OnSourceLost since they won't be passed on without a focused object
                HandleEvent(sourceStateEventData, OnSourceLostEventHandler);

                FocusProvider?.OnSourceLost(sourceStateEventData);
            }
        }

        internal static readonly ExecuteEvents.EventFunction<IMixedRealitySourceStateHandler> OnSourceLostEventHandler =
                delegate (IMixedRealitySourceStateHandler handler, BaseEventData eventData)
                {
                    var casted = ExecuteEvents.ValidateEventData<SourceStateEventData>(eventData);
                    handler.OnSourceLost(casted);
                };

        #endregion Input Source State Events

        #region Input Source Pose Events

        private static readonly ProfilerMarker RaiseSourceTrackingStateChangedPerfMarker = new ProfilerMarker("[MRTK] MixedRealityInputSystem.RaiseTrackingStateChanged");

        /// <inheritdoc />
        public void RaiseSourceTrackingStateChanged(IMixedRealityInputSource source, IMixedRealityController controller, TrackingState state)
        {
            using (RaiseSourceTrackingStateChangedPerfMarker.Auto())
            {
                // Create input event
                sourceTrackingEventData.Initialize(source, controller, state);

                // Pass handler through HandleEvent to perform modal/fallback logic
                HandleEvent(sourceTrackingEventData, OnSourceTrackingChangedEventHandler);
            }
        }

        internal static readonly ExecuteEvents.EventFunction<IMixedRealitySourcePoseHandler> OnSourceTrackingChangedEventHandler =
                delegate (IMixedRealitySourcePoseHandler handler, BaseEventData eventData)
                {
                    var casted = ExecuteEvents.ValidateEventData<SourcePoseEventData<TrackingState>>(eventData);
                    handler.OnSourcePoseChanged(casted);
                };

        private static readonly ProfilerMarker RaiseSourcePositionChangedPerfMarker = new ProfilerMarker("[MRTK] MixedRealityInputSystem.RaiseSourcePositionChanged");

        /// <inheritdoc />
        public void RaiseSourcePositionChanged(IMixedRealityInputSource source, IMixedRealityController controller, Vector2 position)
        {
            using (RaiseSourcePositionChangedPerfMarker.Auto())
            {
                // Create input event
                sourceVector2EventData.Initialize(source, controller, position);

                // Pass handler through HandleEvent to perform modal/fallback logic
                HandleEvent(sourceVector2EventData, OnSourcePoseVector2ChangedEventHandler);
            }
        }

        internal static readonly ExecuteEvents.EventFunction<IMixedRealitySourcePoseHandler> OnSourcePoseVector2ChangedEventHandler =
                delegate (IMixedRealitySourcePoseHandler handler, BaseEventData eventData)
                {
                    var casted = ExecuteEvents.ValidateEventData<SourcePoseEventData<Vector2>>(eventData);
                    handler.OnSourcePoseChanged(casted);
                };

        /// <inheritdoc />
        public void RaiseSourcePositionChanged(IMixedRealityInputSource source, IMixedRealityController controller, Vector3 position)
        {
            using (RaiseSourcePositionChangedPerfMarker.Auto())
            {
                // Create input event
                sourcePositionEventData.Initialize(source, controller, position);

                // Pass handler through HandleEvent to perform modal/fallback logic
                HandleEvent(sourcePositionEventData, OnSourcePositionChangedEventHandler);
            }
        }

        internal static readonly ExecuteEvents.EventFunction<IMixedRealitySourcePoseHandler> OnSourcePositionChangedEventHandler =
                delegate (IMixedRealitySourcePoseHandler handler, BaseEventData eventData)
                {
                    var casted = ExecuteEvents.ValidateEventData<SourcePoseEventData<Vector3>>(eventData);
                    handler.OnSourcePoseChanged(casted);
                };

        private static readonly ProfilerMarker RaiseSourceRotationChangedPerfMarker = new ProfilerMarker("[MRTK] MixedRealityInputSystem.RaiseSourceRotationChanged");

        /// <inheritdoc />
        public void RaiseSourceRotationChanged(IMixedRealityInputSource source, IMixedRealityController controller, Quaternion rotation)
        {
            using (RaiseSourceRotationChangedPerfMarker.Auto())
            {
                // Create input event
                sourceRotationEventData.Initialize(source, controller, rotation);

                // Pass handler through HandleEvent to perform modal/fallback logic
                HandleEvent(sourceRotationEventData, OnSourceRotationChangedEventHandler);
            }
        }

        internal static readonly ExecuteEvents.EventFunction<IMixedRealitySourcePoseHandler> OnSourceRotationChangedEventHandler =
                delegate (IMixedRealitySourcePoseHandler handler, BaseEventData eventData)
                {
                    var casted = ExecuteEvents.ValidateEventData<SourcePoseEventData<Quaternion>>(eventData);
                    handler.OnSourcePoseChanged(casted);
                };

        private static readonly ProfilerMarker RaiseSourcePoseChangedPerfMarker = new ProfilerMarker("[MRTK] MixedRealityInputSystem.RaiseSourcePoseChanged");

        /// <inheritdoc />
        public void RaiseSourcePoseChanged(IMixedRealityInputSource source, IMixedRealityController controller, MixedRealityPose position)
        {
            using (RaiseSourcePoseChangedPerfMarker.Auto())
            {
                // Create input event
                sourcePoseEventData.Initialize(source, controller, position);

                // Pass handler through HandleEvent to perform modal/fallback logic
                HandleEvent(sourcePoseEventData, OnSourcePoseChangedEventHandler);
            }
        }

        internal static readonly ExecuteEvents.EventFunction<IMixedRealitySourcePoseHandler> OnSourcePoseChangedEventHandler =
                delegate (IMixedRealitySourcePoseHandler handler, BaseEventData eventData)
                {
                    var casted = ExecuteEvents.ValidateEventData<SourcePoseEventData<MixedRealityPose>>(eventData);
                    handler.OnSourcePoseChanged(casted);
                };

        #endregion Input Source Pose Events

        #endregion Input Source Events

        #region Focus Events

        private static readonly ProfilerMarker RaisePreFocusChangedPerfMarker = new ProfilerMarker("[MRTK] MixedRealityInputSystem.RaisePreFocusChanged");

        /// <inheritdoc />
        public void RaisePreFocusChanged(IMixedRealityPointer pointer, GameObject oldFocusedObject, GameObject newFocusedObject)
        {
            using (RaisePreFocusChangedPerfMarker.Auto())
            {
                focusEventData.Initialize(pointer, oldFocusedObject, newFocusedObject);

                HandleFocusChangedEvents(focusEventData, OnPreFocusChangedHandler);
            }
        }

        internal static readonly ExecuteEvents.EventFunction<IMixedRealityFocusChangedHandler> OnPreFocusChangedHandler =
                delegate (IMixedRealityFocusChangedHandler handler, BaseEventData eventData)
                {
                    var casted = ExecuteEvents.ValidateEventData<FocusEventData>(eventData);
                    handler.OnBeforeFocusChange(casted);
                };

        private static readonly ProfilerMarker RaiseFocusChangedPerfMarker = new ProfilerMarker("[MRTK] MixedRealityInputSystem.RaiseFocusChanged");

        /// <inheritdoc />
        public void RaiseFocusChanged(IMixedRealityPointer pointer, GameObject oldFocusedObject, GameObject newFocusedObject)
        {
            using (RaiseFocusChangedPerfMarker.Auto())
            {
                focusEventData.Initialize(pointer, oldFocusedObject, newFocusedObject);

                HandleFocusChangedEvents(focusEventData, OnFocusChangedHandler);
            }
        }

        internal static readonly ExecuteEvents.EventFunction<IMixedRealityFocusChangedHandler> OnFocusChangedHandler =
            delegate (IMixedRealityFocusChangedHandler handler, BaseEventData eventData)
            {
                var casted = ExecuteEvents.ValidateEventData<FocusEventData>(eventData);
                handler.OnFocusChanged(casted);
            };

        private static readonly ProfilerMarker RaiseFocusEnterPerfMarker = new ProfilerMarker("[MRTK] MixedRealityInputSystem.RaiseFocusEnter");

        /// <inheritdoc />
        public void RaiseFocusEnter(IMixedRealityPointer pointer, GameObject focusedObject)
        {
            using (RaiseFocusEnterPerfMarker.Auto())
            {
                focusEventData.Initialize(pointer);

                HandleFocusEvent(focusedObject, focusEventData, OnFocusEnterEventHandler);
            }
        }

        internal static readonly ExecuteEvents.EventFunction<IMixedRealityFocusHandler> OnFocusEnterEventHandler =
                delegate (IMixedRealityFocusHandler handler, BaseEventData eventData)
                {
                    var casted = ExecuteEvents.ValidateEventData<FocusEventData>(eventData);
                    handler.OnFocusEnter(casted);
                };

        private static readonly ProfilerMarker RaiseFocusExitPerfMarker = new ProfilerMarker("[MRTK] MixedRealityInputSystem.RaiseFocusExit");

        /// <inheritdoc />
        public void RaiseFocusExit(IMixedRealityPointer pointer, GameObject unfocusedObject)
        {
            using (RaiseFocusExitPerfMarker.Auto())
            {
                focusEventData.Initialize(pointer);

                HandleFocusEvent(unfocusedObject, focusEventData, OnFocusExitEventHandler);
            }
        }

        internal static readonly ExecuteEvents.EventFunction<IMixedRealityFocusHandler> OnFocusExitEventHandler =
                delegate (IMixedRealityFocusHandler handler, BaseEventData eventData)
                {
                    var casted = ExecuteEvents.ValidateEventData<FocusEventData>(eventData);
                    handler.OnFocusExit(casted);
                };

        #endregion Focus Events

        #region Pointers

        #region Pointer Down

        internal static readonly ExecuteEvents.EventFunction<IMixedRealityPointerHandler> OnPointerDownEventHandler =
            delegate (IMixedRealityPointerHandler handler, BaseEventData eventData)
            {
                var casted = ExecuteEvents.ValidateEventData<MixedRealityPointerEventData>(eventData);
                handler.OnPointerDown(casted);
            };

        private static readonly ProfilerMarker RaisePointerDownPerfMarker = new ProfilerMarker("[MRTK] MixedRealityInputSystem.RaisePointerDown");

        /// <inheritdoc />
        public void RaisePointerDown(IMixedRealityPointer pointer, MixedRealityInputAction inputAction, Handedness handedness = Handedness.None, IMixedRealityInputSource inputSource = null)
        {
            using (RaisePointerDownPerfMarker.Auto())
            {
                // Only lock the object if there is a grabbable above in the hierarchy
                Transform currentObject = null;
                GameObject currentGameObject = pointer.Result?.Details.Object;
                if (currentGameObject != null)
                {
                    currentObject = currentGameObject.transform;
                }
                IMixedRealityPointerHandler ancestorPointerHandler = null;
                while (currentObject != null && ancestorPointerHandler == null)
                {
                    foreach (IMixedRealityPointerHandler handler in currentObject.GetComponents<IMixedRealityPointerHandler>())
                    {
                        if (handler is MonoBehaviour behavior && !behavior.enabled)
                        {
                            continue;
                        }
                        ancestorPointerHandler = handler;
                        break;
                    }
                    currentObject = currentObject.transform.parent;
                }
                pointer.IsFocusLocked = ancestorPointerHandler != null;

                pointerEventData.Initialize(pointer, inputAction, handedness, inputSource);

                HandlePointerEvent(pointerEventData, OnPointerDownEventHandler);
            }
        }

        #endregion Pointer Down

        #region Pointer Dragged

        internal static readonly ExecuteEvents.EventFunction<IMixedRealityPointerHandler> OnPointerDraggedEventHandler =
            delegate (IMixedRealityPointerHandler handler, BaseEventData eventData)
            {
                var casted = ExecuteEvents.ValidateEventData<MixedRealityPointerEventData>(eventData);
                handler.OnPointerDragged(casted);
            };

        private static readonly ProfilerMarker RaisePointerDraggedPerfMarker = new ProfilerMarker("[MRTK] MixedRealityInputSystem.RaisePointerDragged");

        /// <inheritdoc />
        public void RaisePointerDragged(IMixedRealityPointer pointer, MixedRealityInputAction inputAction, Handedness handedness = Handedness.None, IMixedRealityInputSource inputSource = null)
        {
            using (RaisePointerDraggedPerfMarker.Auto())
            {
                pointerEventData.Initialize(pointer, inputAction, handedness, inputSource);

                HandlePointerEvent(pointerEventData, OnPointerDraggedEventHandler);
            }
        }

        #endregion Pointer Dragged

        #region Pointer Click

        internal static readonly ExecuteEvents.EventFunction<IMixedRealityPointerHandler> OnInputClickedEventHandler =
                delegate (IMixedRealityPointerHandler handler, BaseEventData eventData)
                {
                    var casted = ExecuteEvents.ValidateEventData<MixedRealityPointerEventData>(eventData);
                    handler.OnPointerClicked(casted);
                };

        /// <inheritdoc />
        private static readonly ProfilerMarker RaisePointerClickedPerfMarker = new ProfilerMarker("[MRTK] MixedRealityInputSystem.RaisePointerClicked");

        public void RaisePointerClicked(IMixedRealityPointer pointer, MixedRealityInputAction inputAction, int count, Handedness handedness = Handedness.None, IMixedRealityInputSource inputSource = null)
        {
            using (RaisePointerClickedPerfMarker.Auto())
            {
                // Create input event
                pointerEventData.Initialize(pointer, inputAction, handedness, inputSource, count);

                HandleClick();
            }
        }

        private void HandleClick()
        {
            // Pass handler through HandleEvent to perform modal/fallback logic
            HandlePointerEvent(pointerEventData, OnInputClickedEventHandler);

            // NOTE: In Unity UI, a "click" happens on every pointer up, so we have RaisePointerUp call the PointerHandler.
        }

        #endregion Pointer Click

        #region Pointer Up

        internal static readonly ExecuteEvents.EventFunction<IMixedRealityPointerHandler> OnPointerUpEventHandler =
            delegate (IMixedRealityPointerHandler handler, BaseEventData eventData)
            {
                var casted = ExecuteEvents.ValidateEventData<MixedRealityPointerEventData>(eventData);
                handler.OnPointerUp(casted);
            };

        private static readonly ProfilerMarker RaisePointerUpPerfMarker = new ProfilerMarker("[MRTK] MixedRealityInputSystem.RaisePointerUp");

        /// <inheritdoc />
        public void RaisePointerUp(IMixedRealityPointer pointer, MixedRealityInputAction inputAction, Handedness handedness = Handedness.None, IMixedRealityInputSource inputSource = null)
        {
            using (RaisePointerUpPerfMarker.Auto())
            {
                pointerEventData.Initialize(pointer, inputAction, handedness, inputSource);

                HandlePointerEvent(pointerEventData, OnPointerUpEventHandler);

                pointer.IsFocusLocked = false;
            }
        }

        #endregion Pointer Up

        #endregion Pointers

        #region Generic Input Events

        #region Input Down

        internal static readonly ExecuteEvents.EventFunction<IMixedRealityInputHandler> OnInputDownEventHandler =
            delegate (IMixedRealityInputHandler handler, BaseEventData eventData)
            {
                var casted = ExecuteEvents.ValidateEventData<InputEventData>(eventData);
                handler.OnInputDown(casted);
            };

        internal static readonly ExecuteEvents.EventFunction<IMixedRealityBaseInputHandler> OnInputDownWithActionEventHandler =
            delegate (IMixedRealityBaseInputHandler handler, BaseEventData eventData)
            {
                var inputData = ExecuteEvents.ValidateEventData<InputEventData>(eventData);
                Debug.Assert(inputData.MixedRealityInputAction != MixedRealityInputAction.None);

                if (handler is IMixedRealityInputHandler inputHandler && !inputHandler.IsNull())
                {
                    inputHandler.OnInputDown(inputData);
                }

                if (handler is IMixedRealityInputActionHandler actionHandler && !actionHandler.IsNull())
                {
                    actionHandler.OnActionStarted(inputData);
                }
            };

        private static readonly ProfilerMarker RaiseOnInputDownPerfMarker = new ProfilerMarker("[MRTK] MixedRealityInputSystem.RaiseOnInputDown");

        /// <inheritdoc />
        public void RaiseOnInputDown(IMixedRealityInputSource source, Handedness handedness, MixedRealityInputAction inputAction)
        {
            using (RaiseOnInputDownPerfMarker.Auto())
            {
                inputAction = ProcessRules(inputAction, true);

                // Create input event
                inputEventData.Initialize(source, handedness, inputAction);

                // Pass handler through HandleEvent to perform modal/fallback logic
                if (inputEventData.MixedRealityInputAction == MixedRealityInputAction.None)
                {
                    HandleEvent(inputEventData, OnInputDownEventHandler);
                }
                else
                {
                    HandleEvent(inputEventData, OnInputDownWithActionEventHandler);
                }
            }
        }

        #endregion Input Down

        #region Input Up

        internal static readonly ExecuteEvents.EventFunction<IMixedRealityInputHandler> OnInputUpEventHandler =
            delegate (IMixedRealityInputHandler handler, BaseEventData eventData)
            {
                var casted = ExecuteEvents.ValidateEventData<InputEventData>(eventData);
                handler.OnInputUp(casted);
            };

        internal static readonly ExecuteEvents.EventFunction<IMixedRealityBaseInputHandler> OnInputUpWithActionEventHandler =
            delegate (IMixedRealityBaseInputHandler handler, BaseEventData eventData)
            {
                var inputData = ExecuteEvents.ValidateEventData<InputEventData>(eventData);
                Debug.Assert(inputData.MixedRealityInputAction != MixedRealityInputAction.None);

                if (handler is IMixedRealityInputHandler inputHandler && !inputHandler.IsNull())
                {
                    inputHandler.OnInputUp(inputData);
                }

                if (handler is IMixedRealityInputActionHandler actionHandler && !actionHandler.IsNull())
                {
                    actionHandler.OnActionEnded(inputData);
                }
            };

        private static readonly ProfilerMarker RaiseOnInputUpPerfMarker = new ProfilerMarker("[MRTK] MixedRealityInputSystem.RaiseOnInputUp");

        /// <inheritdoc />
        public void RaiseOnInputUp(IMixedRealityInputSource source, Handedness handedness, MixedRealityInputAction inputAction)
        {
            using (RaiseOnInputUpPerfMarker.Auto())
            {
                inputAction = ProcessRules(inputAction, false);

                // Create input event
                inputEventData.Initialize(source, handedness, inputAction);

                // Pass handler through HandleEvent to perform modal/fallback logic
                if (inputEventData.MixedRealityInputAction == MixedRealityInputAction.None)
                {
                    HandleEvent(inputEventData, OnInputUpEventHandler);
                }
                else
                {
                    HandleEvent(inputEventData, OnInputUpWithActionEventHandler);
                }
            }
        }

        #endregion Input Up

        #region Float Input Changed

        internal static readonly ExecuteEvents.EventFunction<IMixedRealityInputHandler<float>> OnFloatInputChanged =
            delegate (IMixedRealityInputHandler<float> handler, BaseEventData eventData)
            {
                var casted = ExecuteEvents.ValidateEventData<InputEventData<float>>(eventData);
                handler.OnInputChanged(casted);
            };

        private static readonly ProfilerMarker RaiseFloatInputChangedPerfMarker = new ProfilerMarker("[MRTK] MixedRealityInputSystem.RaiseFloatInputChanged");

        /// <inheritdoc />
        public void RaiseFloatInputChanged(IMixedRealityInputSource source, Handedness handedness, MixedRealityInputAction inputAction, float inputValue)
        {
            using (RaiseFloatInputChangedPerfMarker.Auto())
            {
                inputAction = ProcessRules(inputAction, inputValue);

                // Create input event
                floatInputEventData.Initialize(source, handedness, inputAction, inputValue);

                // Pass handler through HandleEvent to perform modal/fallback logic
                HandleEvent(floatInputEventData, OnFloatInputChanged);
            }
        }

        #endregion Float Input Changed

        #region Input Position Changed

        internal static readonly ExecuteEvents.EventFunction<IMixedRealityInputHandler<Vector2>> OnTwoDoFInputChanged =
            delegate (IMixedRealityInputHandler<Vector2> handler, BaseEventData eventData)
            {
                var casted = ExecuteEvents.ValidateEventData<InputEventData<Vector2>>(eventData);
                handler.OnInputChanged(casted);
            };

        private static readonly ProfilerMarker RaisePositionInputChangedPerfMarker = new ProfilerMarker("[MRTK] MixedRealityInputSystem.RaisePositionInputChanged");

        /// <inheritdoc />
        public void RaisePositionInputChanged(IMixedRealityInputSource source, Handedness handedness, MixedRealityInputAction inputAction, Vector2 inputPosition)
        {
            using (RaisePositionInputChangedPerfMarker.Auto())
            {
                inputAction = ProcessRules(inputAction, inputPosition);

                // Create input event
                vector2InputEventData.Initialize(source, handedness, inputAction, inputPosition);

                // Pass handler through HandleEvent to perform modal/fallback logic
                HandleEvent(vector2InputEventData, OnTwoDoFInputChanged);
            }
        }

        internal static readonly ExecuteEvents.EventFunction<IMixedRealityInputHandler<Vector3>> OnPositionInputChanged =
            delegate (IMixedRealityInputHandler<Vector3> handler, BaseEventData eventData)
            {
                var casted = ExecuteEvents.ValidateEventData<InputEventData<Vector3>>(eventData);
                handler.OnInputChanged(casted);
            };

        /// <inheritdoc />
        public void RaisePositionInputChanged(IMixedRealityInputSource source, Handedness handedness, MixedRealityInputAction inputAction, Vector3 position)
        {
            using (RaisePositionInputChangedPerfMarker.Auto())
            {
                inputAction = ProcessRules(inputAction, position);

                // Create input event
                positionInputEventData.Initialize(source, handedness, inputAction, position);

                // Pass handler through HandleEvent to perform modal/fallback logic
                HandleEvent(positionInputEventData, OnPositionInputChanged);
            }
        }

        #endregion Input Position Changed

        #region Input Rotation Changed

        internal static readonly ExecuteEvents.EventFunction<IMixedRealityInputHandler<Quaternion>> OnRotationInputChanged =
            delegate (IMixedRealityInputHandler<Quaternion> handler, BaseEventData eventData)
            {
                var casted = ExecuteEvents.ValidateEventData<InputEventData<Quaternion>>(eventData);
                handler.OnInputChanged(casted);
            };

        private static readonly ProfilerMarker RaiseRotationInputChangedPerfMarker = new ProfilerMarker("[MRTK] MixedRealityInputSystem.RaiseRotationInputChanged");

        /// <inheritdoc />
        public void RaiseRotationInputChanged(IMixedRealityInputSource source, Handedness handedness, MixedRealityInputAction inputAction, Quaternion rotation)
        {
            using (RaiseRotationInputChangedPerfMarker.Auto())
            {
                inputAction = ProcessRules(inputAction, rotation);

                // Create input event
                rotationInputEventData.Initialize(source, handedness, inputAction, rotation);

                // Pass handler through HandleEvent to perform modal/fallback logic
                HandleEvent(positionInputEventData, OnRotationInputChanged);
            }
        }

        #endregion Input Rotation Changed

        #region Input Pose Changed

        internal static readonly ExecuteEvents.EventFunction<IMixedRealityInputHandler<MixedRealityPose>> OnPoseInputChanged =
            delegate (IMixedRealityInputHandler<MixedRealityPose> handler, BaseEventData eventData)
            {
                var casted = ExecuteEvents.ValidateEventData<InputEventData<MixedRealityPose>>(eventData);
                handler.OnInputChanged(casted);
            };

        private static readonly ProfilerMarker RaisePoseInputChangedPerfMarker = new ProfilerMarker("[MRTK] MixedRealityInputSystem.RaisePoseInputChanged");

        /// <inheritdoc />
        public void RaisePoseInputChanged(IMixedRealityInputSource source, Handedness handedness, MixedRealityInputAction inputAction, MixedRealityPose inputData)
        {
            using (RaisePoseInputChangedPerfMarker.Auto())
            {
                inputAction = ProcessRules(inputAction, inputData);

                // Create input event
                poseInputEventData.Initialize(source, handedness, inputAction, inputData);

                // Pass handler through HandleEvent to perform modal/fallback logic
                HandleEvent(poseInputEventData, OnPoseInputChanged);
            }
        }

        #endregion Input Pose Changed

        #endregion Generic Input Events

        #region Gesture Events

        internal static readonly ExecuteEvents.EventFunction<IMixedRealityGestureHandler> OnGestureStarted =
            delegate (IMixedRealityGestureHandler handler, BaseEventData eventData)
            {
                var casted = ExecuteEvents.ValidateEventData<InputEventData>(eventData);
                handler.OnGestureStarted(casted);
            };

        internal static readonly ExecuteEvents.EventFunction<IMixedRealityBaseInputHandler> OnGestureStartedWithAction =
            delegate (IMixedRealityBaseInputHandler handler, BaseEventData eventData)
            {
                var inputData = ExecuteEvents.ValidateEventData<InputEventData>(eventData);
                Debug.Assert(inputData.MixedRealityInputAction != MixedRealityInputAction.None);

                if (handler is IMixedRealityGestureHandler gestureHandler && !gestureHandler.IsNull())
                {
                    gestureHandler.OnGestureStarted(inputData);
                }

                if (handler is IMixedRealityInputActionHandler actionHandler && !actionHandler.IsNull())
                {
                    actionHandler.OnActionStarted(inputData);
                }
            };

        private static readonly ProfilerMarker RaiseGestureStartedPerfMarker = new ProfilerMarker("[MRTK] MixedRealityInputSystem.RaiseGestureStarted");

        /// <inheritdoc />
        public void RaiseGestureStarted(IMixedRealityController controller, MixedRealityInputAction action)
        {
            using (RaiseGestureStartedPerfMarker.Auto())
            {
                action = ProcessRules(action, true);
                inputEventData.Initialize(controller.InputSource, controller.ControllerHandedness, action);

                if (action == MixedRealityInputAction.None)
                {
                    HandleEvent(inputEventData, OnGestureStarted);
                }
                else
                {
                    HandleEvent(inputEventData, OnGestureStartedWithAction);
                }
            }
        }

        internal static readonly ExecuteEvents.EventFunction<IMixedRealityGestureHandler> OnGestureUpdated =
            delegate (IMixedRealityGestureHandler handler, BaseEventData eventData)
            {
                var casted = ExecuteEvents.ValidateEventData<InputEventData>(eventData);
                handler.OnGestureUpdated(casted);
            };

        private static readonly ProfilerMarker RaiseGestureUpdatedPerfMarker = new ProfilerMarker("[MRTK] MixedRealityInputSystem.RaiseGestureUpdated");

        /// <inheritdoc />
        public void RaiseGestureUpdated(IMixedRealityController controller, MixedRealityInputAction action)
        {
            action = ProcessRules(action, true);
            inputEventData.Initialize(controller.InputSource, controller.ControllerHandedness, action);
            HandleEvent(inputEventData, OnGestureUpdated);
        }

        internal static readonly ExecuteEvents.EventFunction<IMixedRealityGestureHandler<Vector2>> OnGestureVector2PositionUpdated =
                delegate (IMixedRealityGestureHandler<Vector2> handler, BaseEventData eventData)
                {
                    var casted = ExecuteEvents.ValidateEventData<InputEventData<Vector2>>(eventData);
                    handler.OnGestureUpdated(casted);
                };

        /// <inheritdoc />
        public void RaiseGestureUpdated(IMixedRealityController controller, MixedRealityInputAction action, Vector2 inputData)
        {
            using (RaiseGestureUpdatedPerfMarker.Auto())
            {
                action = ProcessRules(action, inputData);
                vector2InputEventData.Initialize(controller.InputSource, controller.ControllerHandedness, action, inputData);
                HandleEvent(vector2InputEventData, OnGestureVector2PositionUpdated);

            }
        }

        internal static readonly ExecuteEvents.EventFunction<IMixedRealityGestureHandler<Vector3>> OnGesturePositionUpdated =
            delegate (IMixedRealityGestureHandler<Vector3> handler, BaseEventData eventData)
            {
                var casted = ExecuteEvents.ValidateEventData<InputEventData<Vector3>>(eventData);
                handler.OnGestureUpdated(casted);
            };

        /// <inheritdoc />
        public void RaiseGestureUpdated(IMixedRealityController controller, MixedRealityInputAction action, Vector3 inputData)
        {
            using (RaiseGestureUpdatedPerfMarker.Auto())
            {
                action = ProcessRules(action, inputData);
                positionInputEventData.Initialize(controller.InputSource, controller.ControllerHandedness, action, inputData);
                HandleEvent(positionInputEventData, OnGesturePositionUpdated);
            }
        }

        internal static readonly ExecuteEvents.EventFunction<IMixedRealityGestureHandler<Quaternion>> OnGestureRotationUpdated =
            delegate (IMixedRealityGestureHandler<Quaternion> handler, BaseEventData eventData)
            {
                var casted = ExecuteEvents.ValidateEventData<InputEventData<Quaternion>>(eventData);
                handler.OnGestureUpdated(casted);
            };

        /// <inheritdoc />
        public void RaiseGestureUpdated(IMixedRealityController controller, MixedRealityInputAction action, Quaternion inputData)
        {
            using (RaiseGestureUpdatedPerfMarker.Auto())
            {
                action = ProcessRules(action, inputData);
                rotationInputEventData.Initialize(controller.InputSource, controller.ControllerHandedness, action, inputData);
                HandleEvent(rotationInputEventData, OnGestureRotationUpdated);
            }
        }

        internal static readonly ExecuteEvents.EventFunction<IMixedRealityGestureHandler<MixedRealityPose>> OnGesturePoseUpdated =
            delegate (IMixedRealityGestureHandler<MixedRealityPose> handler, BaseEventData eventData)
            {
                var casted = ExecuteEvents.ValidateEventData<InputEventData<MixedRealityPose>>(eventData);
                handler.OnGestureUpdated(casted);
            };

        /// <inheritdoc />
        public void RaiseGestureUpdated(IMixedRealityController controller, MixedRealityInputAction action, MixedRealityPose inputData)
        {
            using (RaiseGestureUpdatedPerfMarker.Auto())
            {
                action = ProcessRules(action, inputData);
                poseInputEventData.Initialize(controller.InputSource, controller.ControllerHandedness, action, inputData);
                HandleEvent(poseInputEventData, OnGesturePoseUpdated);
            }
        }

        internal static readonly ExecuteEvents.EventFunction<IMixedRealityGestureHandler> OnGestureCompleted =
            delegate (IMixedRealityGestureHandler handler, BaseEventData eventData)
            {
                var casted = ExecuteEvents.ValidateEventData<InputEventData>(eventData);
                handler.OnGestureCompleted(casted);
            };

        internal static readonly ExecuteEvents.EventFunction<IMixedRealityBaseInputHandler> OnGestureCompletedWithAction =
            delegate (IMixedRealityBaseInputHandler handler, BaseEventData eventData)
            {
                var inputData = ExecuteEvents.ValidateEventData<InputEventData>(eventData);
                Debug.Assert(inputData.MixedRealityInputAction != MixedRealityInputAction.None);

                if (handler is IMixedRealityGestureHandler gestureHandler && !gestureHandler.IsNull())
                {
                    gestureHandler.OnGestureCompleted(inputData);
                }

                if (handler is IMixedRealityInputActionHandler actionHandler && !actionHandler.IsNull())
                {
                    actionHandler.OnActionEnded(inputData);
                }
            };

        private static readonly ProfilerMarker RaiseGestureCompletedPerfMarker = new ProfilerMarker("[MRTK] MixedRealityInputSystem.RaiseGestureCompleted");

        /// <inheritdoc />
        public void RaiseGestureCompleted(IMixedRealityController controller, MixedRealityInputAction action)
        {
            using (RaiseGestureCompletedPerfMarker.Auto())
            {
                action = ProcessRules(action, false);
                inputEventData.Initialize(controller.InputSource, controller.ControllerHandedness, action);

                if (action == MixedRealityInputAction.None)
                {
                    HandleEvent(inputEventData, OnGestureCompleted);
                }
                else
                {
                    HandleEvent(inputEventData, OnGestureCompletedWithAction);
                }
            }
        }

        internal static readonly ExecuteEvents.EventFunction<IMixedRealityGestureHandler<Vector2>> OnGestureVector2PositionCompleted =
                delegate (IMixedRealityGestureHandler<Vector2> handler, BaseEventData eventData)
                {
                    var casted = ExecuteEvents.ValidateEventData<InputEventData<Vector2>>(eventData);
                    handler.OnGestureCompleted(casted);
                };

        /// <inheritdoc />
        public void RaiseGestureCompleted(IMixedRealityController controller, MixedRealityInputAction action, Vector2 inputData)
        {
            using (RaiseGestureCompletedPerfMarker.Auto())
            {
                action = ProcessRules(action, inputData);
                vector2InputEventData.Initialize(controller.InputSource, controller.ControllerHandedness, action, inputData);
                HandleEvent(vector2InputEventData, OnGestureVector2PositionCompleted);
            }
        }

        internal static readonly ExecuteEvents.EventFunction<IMixedRealityGestureHandler<Vector3>> OnGesturePositionCompleted =
            delegate (IMixedRealityGestureHandler<Vector3> handler, BaseEventData eventData)
            {
                var casted = ExecuteEvents.ValidateEventData<InputEventData<Vector3>>(eventData);
                handler.OnGestureCompleted(casted);
            };

        /// <inheritdoc />
        public void RaiseGestureCompleted(IMixedRealityController controller, MixedRealityInputAction action, Vector3 inputData)
        {
            using (RaiseGestureCompletedPerfMarker.Auto())
            {
                action = ProcessRules(action, inputData);
                positionInputEventData.Initialize(controller.InputSource, controller.ControllerHandedness, action, inputData);
                HandleEvent(positionInputEventData, OnGesturePositionCompleted);
            }
        }

        internal static readonly ExecuteEvents.EventFunction<IMixedRealityGestureHandler<Quaternion>> OnGestureRotationCompleted =
            delegate (IMixedRealityGestureHandler<Quaternion> handler, BaseEventData eventData)
            {
                var casted = ExecuteEvents.ValidateEventData<InputEventData<Quaternion>>(eventData);
                handler.OnGestureCompleted(casted);
            };

        /// <inheritdoc />
        public void RaiseGestureCompleted(IMixedRealityController controller, MixedRealityInputAction action, Quaternion inputData)
        {
            using (RaiseGestureCompletedPerfMarker.Auto())
            {
                action = ProcessRules(action, inputData);
                rotationInputEventData.Initialize(controller.InputSource, controller.ControllerHandedness, action, inputData);
                HandleEvent(rotationInputEventData, OnGestureRotationCompleted);
            }
        }

        internal static readonly ExecuteEvents.EventFunction<IMixedRealityGestureHandler<MixedRealityPose>> OnGesturePoseCompleted =
            delegate (IMixedRealityGestureHandler<MixedRealityPose> handler, BaseEventData eventData)
            {
                var casted = ExecuteEvents.ValidateEventData<InputEventData<MixedRealityPose>>(eventData);
                handler.OnGestureCompleted(casted);
            };

        /// <inheritdoc />
        public void RaiseGestureCompleted(IMixedRealityController controller, MixedRealityInputAction action, MixedRealityPose inputData)
        {
            using (RaiseGestureCompletedPerfMarker.Auto())
            {
                action = ProcessRules(action, inputData);
                poseInputEventData.Initialize(controller.InputSource, controller.ControllerHandedness, action, inputData);
                HandleEvent(poseInputEventData, OnGesturePoseCompleted);
            }
        }

        internal static readonly ExecuteEvents.EventFunction<IMixedRealityGestureHandler> OnGestureCanceled =
                delegate (IMixedRealityGestureHandler handler, BaseEventData eventData)
                {
                    var casted = ExecuteEvents.ValidateEventData<InputEventData>(eventData);
                    handler.OnGestureCanceled(casted);
                };

        private static readonly ProfilerMarker RaiseGestureCanceledPerfMarker = new ProfilerMarker("[MRTK] MixedRealityInputSystem.RaiseGestureCanceled");

        /// <inheritdoc />
        public void RaiseGestureCanceled(IMixedRealityController controller, MixedRealityInputAction action)
        {
            using (RaiseGestureCanceledPerfMarker.Auto())
            {
                action = ProcessRules(action, false);
                inputEventData.Initialize(controller.InputSource, controller.ControllerHandedness, action);
                HandleEvent(inputEventData, OnGestureCanceled);
            }
        }

        #endregion Gesture Events

        #region Speech Keyword Events

        internal static readonly ExecuteEvents.EventFunction<IMixedRealitySpeechHandler> OnSpeechKeywordRecognizedEventHandler =
            delegate (IMixedRealitySpeechHandler handler, BaseEventData eventData)
            {
                var casted = ExecuteEvents.ValidateEventData<SpeechEventData>(eventData);
                handler.OnSpeechKeywordRecognized(casted);
            };

        internal static readonly ExecuteEvents.EventFunction<IMixedRealityBaseInputHandler> OnSpeechKeywordRecognizedWithActionEventHandler =
            delegate (IMixedRealityBaseInputHandler handler, BaseEventData eventData)
            {
                var speechData = ExecuteEvents.ValidateEventData<SpeechEventData>(eventData);
                Debug.Assert(speechData.MixedRealityInputAction != MixedRealityInputAction.None);

                if (handler is IMixedRealitySpeechHandler speechHandler && !speechHandler.IsNull())
                {
                    speechHandler.OnSpeechKeywordRecognized(speechData);
                }

                if (handler is IMixedRealityInputActionHandler actionHandler && !actionHandler.IsNull())
                {
                    actionHandler.OnActionStarted(speechData);
                    actionHandler.OnActionEnded(speechData);
                }
            };

        private static readonly ProfilerMarker RaiseSpeechCommandRecognizedPerfMarker = new ProfilerMarker("[MRTK] MixedRealityInputSystem.RaiseSpeechCommandRecognized");

        /// <inheritdoc />
        public void RaiseSpeechCommandRecognized(IMixedRealityInputSource source, RecognitionConfidenceLevel confidence, TimeSpan phraseDuration, DateTime phraseStartTime, SpeechCommands command)
        {
            using (RaiseSpeechCommandRecognizedPerfMarker.Auto())
            {
                // Create input event
                speechEventData.Initialize(source, confidence, phraseDuration, phraseStartTime, command);

                FocusProvider?.OnSpeechKeywordRecognized(speechEventData);

                // Pass handler through HandleEvent to perform modal/fallback logic
                if (command.Action == MixedRealityInputAction.None)
                {
                    HandleEvent(speechEventData, OnSpeechKeywordRecognizedEventHandler);
                }
                else
                {
                    HandleEvent(speechEventData, OnSpeechKeywordRecognizedWithActionEventHandler);
                }
            }
        }

        #endregion Speech Keyword Events

        #region Dictation Events

        internal static readonly ExecuteEvents.EventFunction<IMixedRealityDictationHandler> OnDictationHypothesisEventHandler =
            delegate (IMixedRealityDictationHandler handler, BaseEventData eventData)
            {
                var casted = ExecuteEvents.ValidateEventData<DictationEventData>(eventData);
                handler.OnDictationHypothesis(casted);
            };

        private static readonly ProfilerMarker RaiseDictationHypothesisPerfMarker = new ProfilerMarker("[MRTK] MixedRealityInputSystem.RaiseDictationHypothesis");

        /// <inheritdoc />
        public void RaiseDictationHypothesis(IMixedRealityInputSource source, string dictationHypothesis, AudioClip dictationAudioClip = null)
        {
            using (RaiseDictationHypothesisPerfMarker.Auto())
            {
                // Create input event
                dictationEventData.Initialize(source, dictationHypothesis, dictationAudioClip);

                // Pass handler through HandleEvent to perform modal/fallback logic
                HandleEvent(dictationEventData, OnDictationHypothesisEventHandler);
            }
        }

        internal static readonly ExecuteEvents.EventFunction<IMixedRealityDictationHandler> OnDictationResultEventHandler =
            delegate (IMixedRealityDictationHandler handler, BaseEventData eventData)
            {
                var casted = ExecuteEvents.ValidateEventData<DictationEventData>(eventData);
                handler.OnDictationResult(casted);
            };

        private static readonly ProfilerMarker RaiseDictationResultPerfMarker = new ProfilerMarker("[MRTK] MixedRealityInputSystem.RaiseDictationResult");

        /// <inheritdoc />
        public void RaiseDictationResult(IMixedRealityInputSource source, string dictationResult, AudioClip dictationAudioClip = null)
        {
            using (RaiseDictationResultPerfMarker.Auto())
            {
                // Create input event
                dictationEventData.Initialize(source, dictationResult, dictationAudioClip);

                // Pass handler through HandleEvent to perform modal/fallback logic
                HandleEvent(dictationEventData, OnDictationResultEventHandler);
            }
        }

        internal static readonly ExecuteEvents.EventFunction<IMixedRealityDictationHandler> OnDictationCompleteEventHandler =
            delegate (IMixedRealityDictationHandler handler, BaseEventData eventData)
            {
                var casted = ExecuteEvents.ValidateEventData<DictationEventData>(eventData);
                handler.OnDictationComplete(casted);
            };

        private static readonly ProfilerMarker RaiseDictationCompletePerfMarker = new ProfilerMarker("[MRTK] MixedRealityInputSystem.RaiseDictationComplete");

        /// <inheritdoc />
        public void RaiseDictationComplete(IMixedRealityInputSource source, string dictationResult, AudioClip dictationAudioClip)
        {
            using (RaiseDictationCompletePerfMarker.Auto())
            {
                // Create input event
                dictationEventData.Initialize(source, dictationResult, dictationAudioClip);

                // Pass handler through HandleEvent to perform modal/fallback logic
                HandleEvent(dictationEventData, OnDictationCompleteEventHandler);
            }
        }

        internal static readonly ExecuteEvents.EventFunction<IMixedRealityDictationHandler> OnDictationErrorEventHandler =
            delegate (IMixedRealityDictationHandler handler, BaseEventData eventData)
            {
                var casted = ExecuteEvents.ValidateEventData<DictationEventData>(eventData);
                handler.OnDictationError(casted);
            };

        private static readonly ProfilerMarker RaiseDictationErrorPerfMarker = new ProfilerMarker("[MRTK] MixedRealityInputSystem.RaiseDictationError");

        /// <inheritdoc />
        public void RaiseDictationError(IMixedRealityInputSource source, string dictationResult, AudioClip dictationAudioClip = null)
        {
            using (RaiseDictationErrorPerfMarker.Auto())
            {
                // Create input event
                dictationEventData.Initialize(source, dictationResult, dictationAudioClip);

                // Pass handler through HandleEvent to perform modal/fallback logic
                HandleEvent(dictationEventData, OnDictationErrorEventHandler);
            }
        }

        #endregion Dictation Events

        #region Hand Events

        internal static readonly ExecuteEvents.EventFunction<IMixedRealityHandJointHandler> OnHandJointsUpdatedEventHandler =
            delegate (IMixedRealityHandJointHandler handler, BaseEventData eventData)
            {
                var casted = ExecuteEvents.ValidateEventData<InputEventData<IDictionary<TrackedHandJoint, MixedRealityPose>>>(eventData);

                handler.OnHandJointsUpdated(casted);
            };

        private static readonly ProfilerMarker RaiseHandJointsUpdatedPerfMarker = new ProfilerMarker("[MRTK] MixedRealityInputSystem.RaiseHandJointsUpdated");

        public void RaiseHandJointsUpdated(IMixedRealityInputSource source, Handedness handedness, IDictionary<TrackedHandJoint, MixedRealityPose> jointPoses)
        {
            using (RaiseHandJointsUpdatedPerfMarker.Auto())
            {
                // Create input event
                jointPoseInputEventData.Initialize(source, handedness, MixedRealityInputAction.None, jointPoses);

                // Pass handler through HandleEvent to perform modal/fallback logic
                HandleEvent(jointPoseInputEventData, OnHandJointsUpdatedEventHandler);
            }
        }

        internal static readonly ExecuteEvents.EventFunction<IMixedRealityHandMeshHandler> OnHandMeshUpdatedEventHandler =
            delegate (IMixedRealityHandMeshHandler handler, BaseEventData eventData)
            {
                var casted = ExecuteEvents.ValidateEventData<InputEventData<HandMeshInfo>>(eventData);

                handler.OnHandMeshUpdated(casted);
            };

        private static readonly ProfilerMarker RaiseHandMeshUpdatedPerfMarker = new ProfilerMarker("[MRTK] MixedRealityInputSystem.RaiseHandMeshUpdated");

        public void RaiseHandMeshUpdated(IMixedRealityInputSource source, Handedness handedness, HandMeshInfo handMeshInfo)
        {
            using (RaiseHandMeshUpdatedPerfMarker.Auto())
            {
                // Create input event
                handMeshInputEventData.Initialize(source, handedness, MixedRealityInputAction.None, handMeshInfo);

                // Pass handler through HandleEvent to perform modal/fallback logic
                HandleEvent(handMeshInputEventData, OnHandMeshUpdatedEventHandler);
            }
        }

        internal static readonly ExecuteEvents.EventFunction<IMixedRealityTouchHandler> OnTouchStartedEventHandler =
            delegate (IMixedRealityTouchHandler handler, BaseEventData eventData)
            {
                var casted = ExecuteEvents.ValidateEventData<HandTrackingInputEventData>(eventData);
                handler.OnTouchStarted(casted);
            };

        private static readonly ProfilerMarker RaiseOnTouchStartedPerfMarker = new ProfilerMarker("[MRTK] MixedRealityInputSystem.RaiseOnTouchStarted");

        /// <inheritdoc />
        public void RaiseOnTouchStarted(IMixedRealityInputSource source, IMixedRealityController controller, Handedness handedness, Vector3 touchPoint)
        {
            using (RaiseOnTouchStartedPerfMarker.Auto())
            {
                // Create input event
                handTrackingInputEventData.Initialize(source, controller, handedness, touchPoint);

                // Pass handler through HandleEvent to perform modal/fallback logic
                HandleEvent(handTrackingInputEventData, OnTouchStartedEventHandler);
            }
        }

        internal static readonly ExecuteEvents.EventFunction<IMixedRealityTouchHandler> OnTouchCompletedEventHandler =
            delegate (IMixedRealityTouchHandler handler, BaseEventData eventData)
            {
                var casted = ExecuteEvents.ValidateEventData<HandTrackingInputEventData>(eventData);
                handler.OnTouchCompleted(casted);
            };


        private static readonly ProfilerMarker RaiseOnTouchCompletedPerfMarker = new ProfilerMarker("[MRTK] MixedRealityInputSystem.RaiseOnTouchCompleted");

        /// <inheritdoc />
        public void RaiseOnTouchCompleted(IMixedRealityInputSource source, IMixedRealityController controller, Handedness handedness, Vector3 touchPoint)
        {
            using (RaiseOnTouchCompletedPerfMarker.Auto())
            {
                // Create input event
                handTrackingInputEventData.Initialize(source, controller, handedness, touchPoint);

                // Pass handler through HandleEvent to perform modal/fallback logic
                HandleEvent(handTrackingInputEventData, OnTouchCompletedEventHandler);
            }
        }

        internal static readonly ExecuteEvents.EventFunction<IMixedRealityTouchHandler> OnTouchUpdatedEventHandler =
            delegate (IMixedRealityTouchHandler handler, BaseEventData eventData)
            {
                var casted = ExecuteEvents.ValidateEventData<HandTrackingInputEventData>(eventData);
                handler.OnTouchUpdated(casted);
            };


        private static readonly ProfilerMarker RaiseOnTouchUpdatedPerfMarker = new ProfilerMarker("[MRTK] MixedRealityInputSystem.RaiseOnTouchUpdated");

        /// <inheritdoc />
        public void RaiseOnTouchUpdated(IMixedRealityInputSource source, IMixedRealityController controller, Handedness handedness, Vector3 touchPoint)
        {
            using (RaiseOnTouchUpdatedPerfMarker.Auto())
            {
                // Create input event
                handTrackingInputEventData.Initialize(source, controller, handedness, touchPoint);

                // Pass handler through HandleEvent to perform modal/fallback logic
                HandleEvent(handTrackingInputEventData, OnTouchUpdatedEventHandler);
            }
        }

        #endregion Hand Events

        #endregion Input Events

        #region Rules

        private static readonly ProfilerMarker ProcessRulesInternalPerfMarker = new ProfilerMarker("[MRTK] MixedRealityInputSystem.ProcessRules_Internal");

        private static MixedRealityInputAction ProcessRules_Internal<T1, T2>(MixedRealityInputAction inputAction, T1[] inputActionRules, T2 criteria) where T1 : struct, IInputActionRule<T2>
        {
            using (ProcessRulesInternalPerfMarker.Auto())
            {
                for (int i = 0; i < inputActionRules.Length; i++)
                {
                    if (inputActionRules[i].BaseAction == inputAction && inputActionRules[i].Criteria.Equals(criteria))
                    {
                        if (inputActionRules[i].RuleAction == inputAction)
                        {
                            Debug.LogError("Input Action Rule cannot be the same as the rule's Base Action!");
                            return inputAction;
                        }

                        if (inputActionRules[i].BaseAction.AxisConstraint != inputActionRules[i].RuleAction.AxisConstraint)
                        {
                            Debug.LogError("Input Action Rule doesn't have the same Axis Constraint as the Base Action!");
                            return inputAction;
                        }

                        return inputActionRules[i].RuleAction;
                    }
                }

                return inputAction;
            }
        }

        private MixedRealityInputAction ProcessRules(MixedRealityInputAction inputAction, bool criteria)
        {
            if (CurrentInputActionRulesProfile != null && CurrentInputActionRulesProfile.InputActionRulesDigital?.Length > 0)
            {
                return ProcessRules_Internal(inputAction, CurrentInputActionRulesProfile.InputActionRulesDigital, criteria);
            }

            return inputAction;
        }

        private MixedRealityInputAction ProcessRules(MixedRealityInputAction inputAction, float criteria)
        {
            if (CurrentInputActionRulesProfile != null && CurrentInputActionRulesProfile.InputActionRulesSingleAxis?.Length > 0)
            {
                return ProcessRules_Internal(inputAction, CurrentInputActionRulesProfile.InputActionRulesSingleAxis, criteria);
            }

            return inputAction;
        }

        private MixedRealityInputAction ProcessRules(MixedRealityInputAction inputAction, Vector2 criteria)
        {
            if (CurrentInputActionRulesProfile != null && CurrentInputActionRulesProfile.InputActionRulesDualAxis?.Length > 0)
            {
                return ProcessRules_Internal(inputAction, CurrentInputActionRulesProfile.InputActionRulesDualAxis, criteria);
            }

            return inputAction;
        }

        private MixedRealityInputAction ProcessRules(MixedRealityInputAction inputAction, Vector3 criteria)
        {
            if (CurrentInputActionRulesProfile != null && CurrentInputActionRulesProfile.InputActionRulesVectorAxis?.Length > 0)
            {
                return ProcessRules_Internal(inputAction, CurrentInputActionRulesProfile.InputActionRulesVectorAxis, criteria);
            }

            return inputAction;
        }

        private MixedRealityInputAction ProcessRules(MixedRealityInputAction inputAction, Quaternion criteria)
        {
            if (CurrentInputActionRulesProfile != null && CurrentInputActionRulesProfile.InputActionRulesQuaternionAxis?.Length > 0)
            {
                return ProcessRules_Internal(inputAction, CurrentInputActionRulesProfile.InputActionRulesQuaternionAxis, criteria);
            }

            return inputAction;
        }

        private MixedRealityInputAction ProcessRules(MixedRealityInputAction inputAction, MixedRealityPose criteria)
        {
            if (CurrentInputActionRulesProfile != null && CurrentInputActionRulesProfile.InputActionRulesPoseAxis?.Length > 0)
            {
                return ProcessRules_Internal(inputAction, CurrentInputActionRulesProfile.InputActionRulesPoseAxis, criteria);
            }

            return inputAction;
        }

        #endregion Rules
    }
}
