using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Unity.Collections;
using UnityEngine.InputSystem.Utilities;
////REVIEW: given we have the global ActionPerformed callback, do we really need the per-map callback?
////TODO: remove constraint of not being able to modify bindings while enabled from both actions and maps
////      (because of the sharing of state between multiple maps in an asset, we'd have to extend that constraint
////      to all maps in an asset in order to uphold it properly)
namespace UnityEngine.InputSystem
{
    /// 
    /// A mechanism for collecting a series of input actions (see )
    /// and treating them as a group.
    /// 
    /// 
    /// Each action map is a named collection of bindings and actions. Both are stored
    /// as a flat list. The bindings are available through the 
    /// property and the actions are available through the  property.
    ///
    /// The actions in a map are owned by the map. No action can appear in two maps
    /// at the same time. To find the action map an action belongs to, use the
    ///  property. Note that actions can also stand
    /// on their own and thus do not necessarily need to belong to a map (in which case
    /// the  property is null).
    ///
    /// Within a map, all actions have to have names and each action name must
    /// be unique. The  property of bindings in a map
    /// are resolved within the  in the map. Looking up actions
    /// by name can be done through .
    ///
    /// The  of the map itself can be empty, except if the map is part of
    /// an  in which case it is required to have a name
    /// which also must be unique within the asset.
    ///
    /// Action maps are most useful for grouping actions that contextually
    /// belong together. For example, one common usage is to separate the actions
    /// that can be performed in the UI or in the main menu from those that can
    /// be performed during gameplay. However, even within gameplay, multiple action
    /// maps can be employed. For example, one could have different action maps for
    /// driving and for walking plus one more map for the actions shared between
    /// the two modes.
    ///
    /// Action maps are usually created in the action
    /// editor as part of s. However, they can also be
    /// created standing on their own directly in code or from JSON (see ).
    ///
    /// 
    /// 
    /// // Create a free-standing action map.
    /// var map = new InputActionMap();
    ///
    /// // Add some actions and bindings to it.
    /// map.AddAction("action1", binding: "<Keyboard>/space");
    /// map.AddAction("action2", binding: "<Gamepad>/buttonSouth");
    /// 
    /// 
    ///
    /// Actions in action maps, like actions existing by themselves outside of action
    /// maps, do not actively process input except if enabled. Actions can either
    /// be enabled individually (see  and ) or in bulk by enabling and disabling the
    /// entire map (see  and ).
    /// 
    /// 
    /// 
    [Serializable]
    public sealed class InputActionMap : ICloneable, ISerializationCallbackReceiver, IInputActionCollection2, IDisposable
    {
        /// 
        /// Name of the action map.
        /// 
        /// Name of the action map.
        /// 
        /// For action maps that are part of s, this will always be
        /// a non-null, non-empty string that is unique within the maps in the asset. For action maps
        /// that are standing on their own, this can be null or empty.
        /// 
        public string name => m_Name;
        /// 
        /// If the action map is part of an asset, this refers to the asset. Otherwise it is null.
        /// 
        /// Asset to which the action map belongs.
        public InputActionAsset asset => m_Asset;
        /// 
        /// A stable, unique identifier for the map.
        /// 
        /// Unique ID for the action map.
        /// 
        /// This can be used instead of the name to refer to the action map. Doing so allows referring to the
        /// map such that renaming it does not break references.
        /// 
        /// 
        public Guid id
        {
            get
            {
                if (string.IsNullOrEmpty(m_Id))
                    GenerateId();
                return new Guid(m_Id);
            }
        }
        internal Guid idDontGenerate
        {
            get
            {
                if (string.IsNullOrEmpty(m_Id))
                    return default;
                return new Guid(m_Id);
            }
        }
        /// 
        /// Whether any action in the map is currently enabled.
        /// 
        /// True if any action in  is currently enabled.
        /// 
        /// 
        /// 
        public bool enabled => m_EnabledActionsCount > 0;
        /// 
        /// List of actions contained in the map.
        /// 
        /// Collection of actions belonging to the map.
        /// 
        /// Actions are owned by their map. The same action cannot appear in multiple maps.
        ///
        /// Accessing this property. Note that values returned by the property become invalid if
        /// the setup of actions in a map is changed.
        /// 
        /// 
        public ReadOnlyArray actions => new ReadOnlyArray(m_Actions);
        /// 
        /// List of bindings contained in the map.
        /// 
        /// Collection of bindings in the map.
        /// 
        /// s are owned by action maps and not by individual actions.
        ///
        /// Bindings that trigger actions refer to the action by 
        /// or .
        ///
        /// Accessing this property does not allocate. Note that values returned by the property
        /// become invalid if the setup of bindings in a map is changed.
        /// 
        /// 
        public ReadOnlyArray bindings => new ReadOnlyArray(m_Bindings);
        IEnumerable IInputActionCollection2.bindings => bindings;
        /// 
        /// Control schemes defined for the action map.
        /// 
        /// List of available control schemes.
        /// 
        /// Control schemes can only be defined at the level of s.
        /// For action maps that are part of assets, this property will return the control schemes
        /// from the asset. For free-standing action maps, this will return an empty list.
        /// 
        /// 
        public ReadOnlyArray controlSchemes
        {
            get
            {
                if (m_Asset == null)
                    return new ReadOnlyArray();
                return m_Asset.controlSchemes;
            }
        }
        /// 
        /// Binding mask to apply to all actions in the asset.
        /// 
        /// Optional mask that determines which bindings in the action map to enable.
        /// 
        /// Binding masks can be applied at three different levels: for an entire asset through
        /// , for a specific map through this property,
        /// and for single actions through . By default,
        /// none of the masks will be set (that is, they will be null).
        ///
        /// When an action is enabled, all the binding masks that apply to it are taken into
        /// account. Specifically, this means that any given binding on the action will be
        /// enabled only if it matches the mask applied to the asset, the mask applied
        /// to the map that contains the action, and the mask applied to the action itself.
        /// All the masks are individually optional.
        ///
        /// Masks are matched against bindings using .
        ///
        /// Note that if you modify the masks applicable to an action while it is
        /// enabled, the action's  will get updated immediately to
        /// respect the mask. To avoid repeated binding resolution, it is most efficient
        /// to apply binding masks before enabling actions.
        ///
        /// Binding masks are non-destructive. All the bindings on the action are left
        /// in place. Setting a mask will not affect the value of the 
        /// and  properties.
        /// 
        /// 
        /// 
        /// 
        public InputBinding? bindingMask
        {
            get => m_BindingMask;
            set
            {
                if (m_BindingMask == value)
                    return;
                m_BindingMask = value;
                LazyResolveBindings(fullResolve: true);
            }
        }
        /// 
        /// Set of devices that bindings in the action map can bind to.
        /// 
        /// Optional set of devices to use by bindings in the map.
        /// 
        /// By default (with this property being null), bindings will bind to any of the
        /// controls available through , that is, controls from all
        /// devices in the system will be used.
        ///
        /// By setting this property, binding resolution can instead be restricted to just specific
        /// devices. This restriction can either be applied to an entire asset using  or to specific action maps by using this property. Note that
        /// if both this property and  is set for a specific action
        /// map, the list of devices on the action map will take precedence and the list on the
        /// asset will be ignored for bindings in that action map.
        ///
        /// 
        /// 
        /// // Create an action map containing a single action with a gamepad binding.
        /// var actionMap = new InputActionMap();
        /// var fireAction = actionMap.AddAction("Fire", binding: "<Gamepad>/buttonSouth");
        /// asset.AddActionMap(actionMap);
        ///
        /// // Let's assume we have two gamepads connected. If we enable the
        /// // action map now, the 'Fire' action will bind to both.
        /// actionMap.Enable();
        ///
        /// // This will print two controls.
        /// Debug.Log(string.Join("\n", fireAction.controls));
        ///
        /// // To restrict the setup to just the first gamepad, we can assign
        /// // to the 'devices' property.
        /// actionMap.devices = new InputDevice[] { Gamepad.all[0] };
        ///
        /// // Now this will print only one control.
        /// Debug.Log(string.Join("\n", fireAction.controls));
        /// 
        /// 
        /// 
        /// 
        public ReadOnlyArray? devices
        {
            // Return asset's device list if we have none (only if we're part of an asset).
            get => m_Devices.Get() ?? m_Asset?.devices;
            set
            {
                if (m_Devices.Set(value))
                    LazyResolveBindings(fullResolve: false);
            }
        }
        /// 
        /// Look up an action by name or ID.
        /// 
        /// Name (as in ) or ID (as in )
        /// of the action. Note that matching of names is case-insensitive.
        ///  is null.
        /// No action with the name or ID of 
        /// was found in the action map.
        /// 
        /// This method is equivalent to  except it throws KeyNotFoundException
        /// if no action with the given name or ID can be found.
        /// 
        /// 
        /// 
        /// 
        public InputAction this[string actionNameOrId]
        {
            get
            {
                if (actionNameOrId == null)
                    throw new ArgumentNullException(nameof(actionNameOrId));
                var action = FindAction(actionNameOrId);
                if (action == null)
                    throw new KeyNotFoundException($"Cannot find action '{actionNameOrId}'");
                return action;
            }
        }
        ////REVIEW: inconsistent naming; elsewhere we use "onActionTriggered" (which in turn is inconsistent with InputAction.started etc)
        /// 
        /// Add or remove a callback that is triggered when an action in the map changes its 
        /// phase.
        /// 
        /// 
        /// 
        /// 
        public event Action actionTriggered
        {
            add => m_ActionCallbacks.AddCallback(value);
            remove => m_ActionCallbacks.RemoveCallback(value);
        }
        public InputActionMap()
        {
        }
        /// 
        /// Construct an action map with the given name.
        /// 
        /// Name to give to the action map. By default null, i.e. does
        /// not assign a name to the map.
        public InputActionMap(string name)
            : this()
        {
            m_Name = name;
        }
        /// 
        /// Release internal state held on to by the action map.
        /// 
        /// 
        /// Once actions in a map are enabled, the map will allocate a block of state internally that
        /// it will hold on to until disposed of. All actions in the map will share the same internal
        /// state. Also, if the map is part of an  all maps and actions
        /// in the same asset will share the same internal state.
        ///
        /// Note that the internal state holds on to GC heap memory as well as memory from the
        /// unmanaged, C++ heap.
        /// 
        public void Dispose()
        {
            m_State?.Dispose();
        }
        internal int FindActionIndex(string nameOrId)
        {
            ////REVIEW: have transient lookup table? worth optimizing this?
            ////   Ideally, this should at least be an InternedString comparison but due to serialization,
            ////   that's quite tricky.
            if (string.IsNullOrEmpty(nameOrId))
                return -1;
            if (m_Actions == null)
                return -1;
            // First time we hit this method, we populate the lookup table.
            SetUpActionLookupTable();
            var actionCount = m_Actions.Length;
            var isOldBracedFormat = nameOrId.StartsWith("{") && nameOrId.EndsWith("}");
            if (isOldBracedFormat)
            {
                var length = nameOrId.Length - 2;
                for (var i = 0; i < actionCount; ++i)
                {
                    if (string.Compare(m_Actions[i].m_Id, 0, nameOrId, 1, length) == 0)
                        return i;
                }
            }
            if (m_ActionIndexByNameOrId.TryGetValue(nameOrId, out var actionIndex))
                return actionIndex;
            for (var i = 0; i < actionCount; ++i)
            {
                var action = m_Actions[i];
                if (action.m_Id == nameOrId || string.Compare(m_Actions[i].m_Name, nameOrId, StringComparison.InvariantCultureIgnoreCase) == 0)
                    return i;
            }
            return InputActionState.kInvalidIndex;
        }
        private void SetUpActionLookupTable()
        {
            if (m_ActionIndexByNameOrId != null || m_Actions == null)
                return;
            m_ActionIndexByNameOrId = new Dictionary();
            var actionCount = m_Actions.Length;
            for (var i = 0; i < actionCount; ++i)
            {
                var action = m_Actions[i];
                // We want to make sure an action ID cannot change *after* we have created the table.
                // NOTE: The *name* of an action, however, *may* change.
                action.MakeSureIdIsInPlace();
                // We create two lookup paths for each action:
                // (1) By case-sensitive name.
                // (2) By GUID string.
                m_ActionIndexByNameOrId[action.name] = i;
                m_ActionIndexByNameOrId[action.m_Id] = i;
            }
        }
        internal void ClearActionLookupTable()
        {
            m_ActionIndexByNameOrId?.Clear();
        }
        private int FindActionIndex(Guid id)
        {
            if (m_Actions == null)
                return InputActionState.kInvalidIndex;
            var actionCount = m_Actions.Length;
            for (var i = 0; i < actionCount; ++i)
                if (m_Actions[i].idDontGenerate == id)
                    return i;
            return InputActionState.kInvalidIndex;
        }
        /// 
        /// Find an action in the map by name or ID.
        /// 
        /// Name (as in ) or ID (as in )
        /// of the action. Note that matching of names is case-insensitive.
        /// The action with the given name or ID or null if no matching action
        /// was found.
        ///  is null.
        /// 
        public InputAction FindAction(string actionNameOrId, bool throwIfNotFound = false)
        {
            if (actionNameOrId == null)
                throw new ArgumentNullException(nameof(actionNameOrId));
            var index = FindActionIndex(actionNameOrId);
            if (index == -1)
            {
                if (throwIfNotFound)
                    throw new ArgumentException($"No action '{actionNameOrId}' in '{this}'", nameof(actionNameOrId));
                return null;
            }
            return m_Actions[index];
        }
        /// 
        /// Find an action by ID.
        /// 
        /// ID (as in ) of the action.
        /// The action with the given ID or null if no action in the map has
        /// the given ID.
        /// 
        public InputAction FindAction(Guid id)
        {
            var index = FindActionIndex(id);
            if (index == -1)
                return null;
            return m_Actions[index];
        }
        /// 
        /// Check whether there are any bindings in the action map that can bind to
        /// controls on the given device.
        /// 
        /// An input device.
        /// True if any of the bindings in the map can resolve to controls on the device, false otherwise.
        ///  is null.
        /// 
        /// The logic is entirely based on the contents of  and, more specifically,
        ///  of each binding. Each path is checked using . If any path matches, the method returns true.
        ///
        /// Properties such as  and  are ignored.
        ///
        /// 
        /// 
        /// // Create action map with two actions and bindings.
        /// var actionMap = new InputActionMap();
        /// actionMap.AddAction("action1", binding: "<Gamepad>/buttonSouth");
        /// actionMap.AddAction("action2", binding: "<XRController{LeftHand}>/{PrimaryAction}");
        ///
        /// //
        /// var gamepad = InputSystem.AddDevice<Gamepad>();
        /// var xrController = InputSystem.AddDevice<XRController>();
        ///
        /// // Returns true:
        /// actionMap.IsUsableWith(gamepad);
        ///
        /// // Returns false: (the XRController does not have the LeftHand usage assigned to it)
        /// actionMap.IsUsableWith(xrController);
        /// 
        /// 
        /// 
        public bool IsUsableWithDevice(InputDevice device)
        {
            if (device == null)
                throw new ArgumentNullException(nameof(device));
            if (m_Bindings == null)
                return false;
            foreach (var binding in m_Bindings)
            {
                var path = binding.effectivePath;
                if (string.IsNullOrEmpty(path))
                    continue;
                if (InputControlPath.Matches(path, device))
                    return true;
            }
            return false;
        }
        /// 
        /// Enable all the actions in the map.
        /// 
        /// 
        /// This is equivalent to calling  on each
        /// action in , but is more efficient as the actions
        /// will get enabled in bulk.
        /// 
        /// 
        /// 
        public void Enable()
        {
            if (m_Actions == null || m_EnabledActionsCount == m_Actions.Length)
                return;
            ResolveBindingsIfNecessary();
            m_State.EnableAllActions(this);
        }
        /// 
        /// Disable all the actions in the map.
        /// 
        /// 
        /// This is equivalent to calling  on each
        /// action in , but is more efficient as the actions
        /// will get disabled in bulk.
        /// 
        /// 
        /// 
        public void Disable()
        {
            if (!enabled)
                return;
            m_State.DisableAllActions(this);
        }
        /// 
        /// Produce an identical copy of the action map with its actions and bindings.
        /// 
        /// A copy of the action map.
        /// 
        /// If the action map is part of an , the clone will not
        /// be. It will be a free-standing action map and  will be null.
        ///
        /// Note that the IDs for the map itself as well as for its  and
        ///  are not copied. Instead, new IDs will be assigned. Also, callbacks
        /// installed on actions or on the map itself will not be copied over.
        /// 
        public InputActionMap Clone()
        {
            Debug.Assert(m_SingletonAction == null, "Internal (hidden) action maps of singleton actions should not be cloned");
            var clone = new InputActionMap
            {
                m_Name = m_Name
            };
            // Clone actions.
            if (m_Actions != null)
            {
                var actionCount = m_Actions.Length;
                var actions = new InputAction[actionCount];
                for (var i = 0; i < actionCount; ++i)
                {
                    var original = m_Actions[i];
                    actions[i] = new InputAction
                    {
                        m_Name = original.m_Name,
                        m_ActionMap = clone,
                        m_Type = original.m_Type,
                        m_Interactions = original.m_Interactions,
                        m_Processors = original.m_Processors,
                        m_ExpectedControlType = original.m_ExpectedControlType,
                    };
                }
                clone.m_Actions = actions;
            }
            // Clone bindings.
            if (m_Bindings != null)
            {
                var bindingCount = m_Bindings.Length;
                var bindings = new InputBinding[bindingCount];
                Array.Copy(m_Bindings, 0, bindings, 0, bindingCount);
                for (var i = 0; i < bindingCount; ++i)
                    bindings[i].m_Id = default;
                clone.m_Bindings = bindings;
            }
            return clone;
        }
        object ICloneable.Clone()
        {
            return Clone();
        }
        /// 
        /// Return true if the action map contains the given action.
        /// 
        /// An input action. Can be null.
        /// True if the action map contains , false otherwise.
        public bool Contains(InputAction action)
        {
            if (action == null)
                return false;
            return action.actionMap == this;
        }
        /// 
        /// Return a string representation of the action map useful for debugging.
        /// 
        /// A string representation of the action map.
        /// 
        /// For unnamed action maps, this will always be "<Unnamed Action Map>".
        /// 
        public override string ToString()
        {
            if (m_Asset != null)
                return $"{m_Asset}:{m_Name}";
            if (!string.IsNullOrEmpty(m_Name))
                return m_Name;
            return "";
        }
        /// 
        /// Enumerate the actions in the map.
        /// 
        /// An enumerator going over the actions in the map.
        /// 
        /// This method supports to generically iterate over the actions in a map. However, it will usually
        /// lead to GC allocation. Iterating directly over  avoids allocating GC memory.
        /// 
        public IEnumerator GetEnumerator()
        {
            return actions.GetEnumerator();
        }
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
        // The state we persist is pretty much just a name, a flat list of actions, and a flat
        // list of bindings. The rest is state we keep at runtime when a map is in use.
        [SerializeField] internal string m_Name;
        [SerializeField] internal string m_Id; // Can't serialize System.Guid and Unity's GUID is editor only.
        [SerializeField] internal InputActionAsset m_Asset;
        /// 
        /// List of actions in this map.
        /// 
        [SerializeField] internal InputAction[] m_Actions;
        /// 
        /// List of bindings in this map.
        /// 
        /// 
        /// For singleton actions, we ensure this is always the same as .
        /// 
        [SerializeField] internal InputBinding[] m_Bindings;
        // These fields are caches. If m_Bindings is modified, these are thrown away
        // and re-computed only if needed.
        // NOTE: Because InputBindings are structs, m_BindingsForEachAction actually duplicates each binding
        //       (only in the case where m_Bindings has scattered references to actions).
        ////REVIEW: this will lead to problems when overrides are thrown into the mix
        /// 
        /// For each entry in , a slice of this array corresponds to the
        /// action's bindings.
        /// 
        /// 
        /// Ideally, this array is the same as  (the same as in literally reusing the
        /// same array). However, we have no guarantee that  is sorted by actions. In case it
        /// isn't, we create a separate array with the bindings sorted by action and have each action reference
        /// a slice through  and .
        /// 
        /// 
        [NonSerialized] private InputBinding[] m_BindingsForEachAction;
        [NonSerialized] private InputControl[] m_ControlsForEachAction;
        /// 
        /// Number of actions currently enabled in the map.
        /// 
        /// 
        /// This should only be written to by .
        /// 
        [NonSerialized] internal int m_EnabledActionsCount;
        // Action maps that are created internally by singleton actions to hold their data
        // are never exposed and never serialized so there is no point allocating an m_Actions
        // array.
        [NonSerialized] internal InputAction m_SingletonAction;
        [NonSerialized] internal int m_MapIndexInState = InputActionState.kInvalidIndex;
        /// 
        /// Current execution state.
        /// 
        /// 
        /// Initialized when map (or any action in it) is first enabled.
        /// 
        [NonSerialized] internal InputActionState m_State;
        [NonSerialized] internal InputBinding? m_BindingMask;
        [NonSerialized] private Flags m_Flags;
        [NonSerialized] internal int m_ParameterOverridesCount;
        [NonSerialized] internal InputActionRebindingExtensions.ParameterOverride[] m_ParameterOverrides;
        [NonSerialized] internal DeviceArray m_Devices;
        [NonSerialized] internal CallbackArray> m_ActionCallbacks;
        [NonSerialized] internal Dictionary m_ActionIndexByNameOrId;
        private bool needToResolveBindings
        {
            get => (m_Flags & Flags.NeedToResolveBindings) != 0;
            set
            {
                if (value)
                    m_Flags |= Flags.NeedToResolveBindings;
                else
                    m_Flags &= ~Flags.NeedToResolveBindings;
            }
        }
        private bool bindingResolutionNeedsFullReResolve
        {
            get => (m_Flags & Flags.BindingResolutionNeedsFullReResolve) != 0;
            set
            {
                if (value)
                    m_Flags |= Flags.BindingResolutionNeedsFullReResolve;
                else
                    m_Flags &= ~Flags.BindingResolutionNeedsFullReResolve;
            }
        }
        private bool controlsForEachActionInitialized
        {
            get => (m_Flags & Flags.ControlsForEachActionInitialized) != 0;
            set
            {
                if (value)
                    m_Flags |= Flags.ControlsForEachActionInitialized;
                else
                    m_Flags &= ~Flags.ControlsForEachActionInitialized;
            }
        }
        private bool bindingsForEachActionInitialized
        {
            get => (m_Flags & Flags.BindingsForEachActionInitialized) != 0;
            set
            {
                if (value)
                    m_Flags |= Flags.BindingsForEachActionInitialized;
                else
                    m_Flags &= ~Flags.BindingsForEachActionInitialized;
            }
        }
        [Flags]
        private enum Flags
        {
            NeedToResolveBindings = 1 << 0,
            BindingResolutionNeedsFullReResolve = 1 << 1,
            ControlsForEachActionInitialized = 1 << 2,
            BindingsForEachActionInitialized = 1 << 3,
        }
        internal static int s_DeferBindingResolution;
        internal struct DeviceArray
        {
            private bool m_HaveValue;
            private int m_DeviceCount;
            private InputDevice[] m_DeviceArray; // May have extra capacity; we won't let go once allocated.
            public int IndexOf(InputDevice device)
            {
                return m_DeviceArray.IndexOfReference(device, m_DeviceCount);
            }
            public bool Remove(InputDevice device)
            {
                var index = IndexOf(device);
                if (index < 0)
                    return false;
                m_DeviceArray.EraseAtWithCapacity(ref m_DeviceCount, index);
                return true;
            }
            public ReadOnlyArray? Get()
            {
                if (!m_HaveValue)
                    return null;
                return new ReadOnlyArray(m_DeviceArray, 0, m_DeviceCount);
            }
            public bool Set(ReadOnlyArray? devices)
            {
                if (!devices.HasValue)
                {
                    if (!m_HaveValue)
                        return false; // No change.
                    if (m_DeviceCount > 0)
                        Array.Clear(m_DeviceArray, 0, m_DeviceCount);
                    m_DeviceCount = 0;
                    m_HaveValue = false;
                }
                else
                {
                    // See if the array actually changes content. Avoids re-resolving when there
                    // is no need to.
                    var array = devices.Value;
                    if (m_HaveValue && array.Count == m_DeviceCount && array.HaveEqualReferences(m_DeviceArray, m_DeviceCount))
                        return false;
                    if (m_DeviceCount > 0)
                        m_DeviceArray.Clear(ref m_DeviceCount);
                    m_HaveValue = true;
                    m_DeviceCount = 0;
                    ArrayHelpers.AppendListWithCapacity(ref m_DeviceArray, ref m_DeviceCount, array);
                }
                return true;
            }
        }
        /// 
        /// Return the list of bindings for just the given actions.
        /// 
        /// 
        /// 
        /// 
        /// The bindings for a single action may be contiguous in  or may be scattered
        /// around. We don't keep persistent storage for these and instead set up a transient
        /// array if and when bindings are queried directly from an action. In the simple case,
        /// we don't even need a separate array but rather just need to find out which slice in the
        /// bindings array corresponds to which action.
        ///
        /// NOTE: Bindings for individual actions aren't queried by the system itself during normal
        ///       runtime operation so we only do this for cases where the user asks for the
        ///       information. If the user never asks for bindings or controls on a per-action basis,
        ///       none of this data gets initialized.
        /// 
        internal ReadOnlyArray GetBindingsForSingleAction(InputAction action)
        {
            Debug.Assert(action != null, "Action cannot be null");
            Debug.Assert(action.m_ActionMap == this, "Action must be in action map");
            Debug.Assert(!action.isSingletonAction || m_SingletonAction == action, "Action is not a singleton action");
            // See if we need to refresh.
            if (!bindingsForEachActionInitialized)
                SetUpPerActionControlAndBindingArrays();
            return new ReadOnlyArray(m_BindingsForEachAction, action.m_BindingsStartIndex,
                action.m_BindingsCount);
        }
        internal ReadOnlyArray GetControlsForSingleAction(InputAction action)
        {
            Debug.Assert(m_State != null);
            Debug.Assert(m_MapIndexInState != InputActionState.kInvalidIndex);
            Debug.Assert(m_Actions != null);
            Debug.Assert(action != null);
            Debug.Assert(action.m_ActionMap == this);
            Debug.Assert(!action.isSingletonAction || m_SingletonAction == action);
            if (!controlsForEachActionInitialized)
                SetUpPerActionControlAndBindingArrays();
            return new ReadOnlyArray(m_ControlsForEachAction, action.m_ControlStartIndex,
                action.m_ControlCount);
        }
        /// 
        /// Collect data from  and  such that we can
        /// we can cleanly expose it from  and .
        /// 
        /// 
        /// We set up per-action caches the first time their information is requested. Internally, we do not
        /// use those arrays and thus they will not get set up by default.
        ///
        /// Note that it is important to allow to call this method at a point where we have not resolved
        /// controls yet (i.e.  is null). Otherwise, using 
        /// may trigger a control resolution which would be surprising.
        /// 
        private unsafe void SetUpPerActionControlAndBindingArrays()
        {
            // Handle case where we don't have any bindings.
            if (m_Bindings == null)
            {
                m_ControlsForEachAction = null;
                m_BindingsForEachAction = null;
                controlsForEachActionInitialized = true;
                bindingsForEachActionInitialized = true;
                return;
            }
            if (m_SingletonAction != null)
            {
                // Dead simple case: map is internally owned by action. The entire
                // list of bindings is specific to the action.
                Debug.Assert(m_Bindings == m_SingletonAction.m_SingletonActionBindings,
                    "For singleton action, bindings array must match that of the action");
                m_BindingsForEachAction = m_Bindings;
                m_ControlsForEachAction = m_State?.controls;
                m_SingletonAction.m_BindingsStartIndex = 0;
                m_SingletonAction.m_BindingsCount = m_Bindings.Length;
                m_SingletonAction.m_ControlStartIndex = 0;
                m_SingletonAction.m_ControlCount = m_State?.totalControlCount ?? 0;
                // Only complication, InputActionState allows a control to appear multiple times
                // on the same action and InputAction.controls[] doesn't.
                if (m_ControlsForEachAction.HaveDuplicateReferences(0, m_SingletonAction.m_ControlCount))
                {
                    var numControls = 0;
                    var controls = new InputControl[m_SingletonAction.m_ControlCount];
                    for (var i = 0; i < m_SingletonAction.m_ControlCount; ++i)
                    {
                        if (!controls.ContainsReference(m_ControlsForEachAction[i]))
                        {
                            controls[numControls] = m_ControlsForEachAction[i];
                            ++numControls;
                        }
                    }
                    m_ControlsForEachAction = controls;
                    m_SingletonAction.m_ControlCount = numControls;
                }
            }
            else
            {
                ////REVIEW: now that we have per-action binding information in UnmanagedMemory, this here can likely be done more easily
                // Go through all bindings and slice them out to individual actions.
                Debug.Assert(m_Actions != null, "Action map is associated with action but action map has no array of actions"); // Action isn't a singleton so this has to be true.
                var mapIndices = m_State?.FetchMapIndices(this) ?? new InputActionState.ActionMapIndices();
                // Reset state on each action. Important if we have actions that are no longer
                // referred to by bindings.
                for (var i = 0; i < m_Actions.Length; ++i)
                {
                    var action = m_Actions[i];
                    action.m_BindingsCount = 0;
                    action.m_BindingsStartIndex = -1;
                    action.m_ControlCount = 0;
                    action.m_ControlStartIndex = -1;
                }
                // Count bindings on each action.
                // After this loop, we can have one of two situations:
                // 1) The bindings for any action X start at some index N and occupy the next m_BindingsCount slots.
                // 2) The bindings for some or all actions are scattered across non-contiguous chunks of the array.
                var bindingCount = m_Bindings.Length;
                for (var i = 0; i < bindingCount; ++i)
                {
                    var action = FindAction(m_Bindings[i].action);
                    if (action != null)
                        ++action.m_BindingsCount;
                }
                // Collect the bindings and controls and bundle them into chunks.
                var newBindingsArrayIndex = 0;
                if (m_State != null && (m_ControlsForEachAction == null || m_ControlsForEachAction.Length != mapIndices.controlCount))
                {
                    if (mapIndices.controlCount == 0)
                        m_ControlsForEachAction = null;
                    else
                        m_ControlsForEachAction = new InputControl[mapIndices.controlCount];
                }
                InputBinding[] newBindingsArray = null;
                var currentControlIndex = 0;
                for (var currentBindingIndex = 0; currentBindingIndex < m_Bindings.Length;)
                {
                    var currentAction = FindAction(m_Bindings[currentBindingIndex].action);
                    if (currentAction == null || currentAction.m_BindingsStartIndex != -1)
                    {
                        // Skip bindings not targeting an action or bindings we have already processed
                        // (when gathering bindings for a single actions scattered across the array we may have
                        // skipping ahead).
                        ++currentBindingIndex;
                        continue;
                    }
                    // Bindings for current action start at current index.
                    currentAction.m_BindingsStartIndex = newBindingsArray != null
                        ? newBindingsArrayIndex
                        : currentBindingIndex;
                    currentAction.m_ControlStartIndex = currentControlIndex;
                    // Collect all bindings for the action. As part of that, also copy the controls
                    // for each binding over to m_ControlsForEachAction.
                    var bindingCountForCurrentAction = currentAction.m_BindingsCount;
                    Debug.Assert(bindingCountForCurrentAction > 0);
                    var sourceBindingToCopy = currentBindingIndex;
                    for (var i = 0; i < bindingCountForCurrentAction; ++i)
                    {
                        // See if we've come across a binding that doesn't belong to our currently looked at action.
                        if (FindAction(m_Bindings[sourceBindingToCopy].action) != currentAction)
                        {
                            // Yes, we have. Means the bindings for our actions are scattered in m_Bindings and
                            // we need to collect them.
                            // If this is the first action that has its bindings scattered around, switch to
                            // having a separate bindings array and copy whatever bindings we already processed
                            // over to it.
                            if (newBindingsArray == null)
                            {
                                newBindingsArray = new InputBinding[m_Bindings.Length];
                                newBindingsArrayIndex = sourceBindingToCopy;
                                Array.Copy(m_Bindings, 0, newBindingsArray, 0, sourceBindingToCopy);
                            }
                            // Find the next binding belonging to the action. We've counted bindings for
                            // the action in the previous pass so we know exactly how many bindings we
                            // can expect.
                            do
                            {
                                ++sourceBindingToCopy;
                                Debug.Assert(sourceBindingToCopy < m_Bindings.Length);
                            }
                            while (FindAction(m_Bindings[sourceBindingToCopy].action) != currentAction);
                        }
                        else if (currentBindingIndex == sourceBindingToCopy)
                            ++currentBindingIndex;
                        // Copy binding over to new bindings array, if need be.
                        if (newBindingsArray != null)
                            newBindingsArray[newBindingsArrayIndex++] = m_Bindings[sourceBindingToCopy];
                        // Copy controls for binding, if we have resolved controls already and if the
                        // binding isn't a composite (they refer to the controls from all of their part bindings
                        // but do not really resolve to controls themselves).
                        if (m_State != null && !m_Bindings[sourceBindingToCopy].isComposite)
                        {
                            ref var bindingState = ref m_State.bindingStates[mapIndices.bindingStartIndex + sourceBindingToCopy];
                            var controlCountForBinding = bindingState.controlCount;
                            if (controlCountForBinding > 0)
                            {
                                // Internally, we allow several bindings on a given action to resolve to the same control.
                                // Externally, however, InputAction.controls[] is a set and thus should not contain duplicates.
                                // So, instead of just doing a straight copy here, we copy controls one by one.
                                var controlStartIndexForBinding = bindingState.controlStartIndex;
                                for (var n = 0; n < controlCountForBinding; ++n)
                                {
                                    var control = m_State.controls[controlStartIndexForBinding + n];
                                    if (!m_ControlsForEachAction.ContainsReference(currentAction.m_ControlStartIndex,
                                        currentAction.m_ControlCount, control))
                                    {
                                        m_ControlsForEachAction[currentControlIndex] = control;
                                        ++currentControlIndex;
                                        ++currentAction.m_ControlCount;
                                    }
                                }
                            }
                        }
                        ++sourceBindingToCopy;
                    }
                }
                if (newBindingsArray == null)
                {
                    // Bindings are already clustered by action in m_Bindings
                    // so we can just stick to having one array only.
                    m_BindingsForEachAction = m_Bindings;
                }
                else
                {
                    // Bindings are not clustered by action in m_Bindings so
                    // we had to allocate a separate array where the bindings are sorted.
                    m_BindingsForEachAction = newBindingsArray;
                }
            }
            controlsForEachActionInitialized = true;
            bindingsForEachActionInitialized = true;
        }
        internal void OnWantToChangeSetup()
        {
            if (asset != null)
            {
                foreach (var assetMap in asset.actionMaps)
                    if (assetMap.enabled)
                        throw new InvalidOperationException(
                            $"Cannot add, remove, or change elements of InputActionAsset {asset} while one or more of its actions are enabled");
            }
            else if (enabled)
            {
                throw new InvalidOperationException(
                    $"Cannot add, remove, or change elements of InputActionMap {this} while one or more of its actions are enabled");
            }
        }
        internal void OnSetupChanged()
        {
            if (m_Asset != null)
            {
                m_Asset.MarkAsDirty();
                foreach (var map in m_Asset.actionMaps)
                    map.m_State = default;
            }
            else
            {
                m_State = default;
            }
            ClearCachedActionData();
            LazyResolveBindings(fullResolve: true);
        }
        internal void OnBindingModified()
        {
            ClearCachedActionData();
            LazyResolveBindings(fullResolve: true);
        }
        ////TODO: re-use allocations such that only grow the arrays and hit zero GC allocs when we already have enough memory
        internal void ClearCachedActionData(bool onlyControls = false)
        {
            if (!onlyControls)
            {
                bindingsForEachActionInitialized = false;
                m_BindingsForEachAction = default;
                m_ActionIndexByNameOrId = default;
            }
            controlsForEachActionInitialized = false;
            m_ControlsForEachAction = default;
        }
        internal void GenerateId()
        {
            m_Id = Guid.NewGuid().ToString();
        }
        /// 
        /// Resolve bindings right away if we have to. Otherwise defer it to when we next need
        /// the bindings.
        /// 
        internal bool LazyResolveBindings(bool fullResolve)
        {
            // Clear cached controls for actions. Don't need to necessarily clear m_BindingsForEachAction.
            m_ControlsForEachAction = null;
            controlsForEachActionInitialized = false;
            // If we haven't had to resolve bindings yet, we can wait until when we
            // actually have to.
            if (m_State == null)
                return false;
            // We used to defer binding resolution here in case the map had no enabled actions. That behavior,
            // however, leads to rather unpredictable BoundControlsChanged notifications (especially for
            // rebinding UIs), so now we just always re-resolve anything that ever had an InputActionState
            // created. Unfortunately, this can lead to some unnecessary re-resolving.
            needToResolveBindings = true;
            bindingResolutionNeedsFullReResolve |= fullResolve;
            if (s_DeferBindingResolution > 0)
                return false;
            // Have to do it straight away.
            ResolveBindings();
            return true;
        }
        internal bool ResolveBindingsIfNecessary()
        {
            // NOTE: We only check locally for the current map here. When there are multiple maps
            //       in an asset, we may have maps that require re-resolution while others don't.
            //       We only resolve if a map is used that needs resolution to happen. Note that
            //       this will still resolve bindings for *all* maps in the asset.
            if (m_State == null || needToResolveBindings)
            {
                if (m_State != null && m_State.isProcessingControlStateChange)
                {
                    Debug.Assert(s_DeferBindingResolution > 0, "While processing control state changes, binding resolution should be suppressed");
                    return false;
                }
                ResolveBindings();
                return true;
            }
            return false;
        }
        // We have three different starting scenarios for binding resolution:
        //
        // (1) From scratch.
        //     There is no InputActionState and we resolve everything from a completely fresh start. This happens when
        //     we either have not resolved bindings at all yet or when something touches the action setup (e.g. adds
        //     or removes an action or binding) and we thus throw away the existing InputActionState.
        //     NOTE:
        //      * Actions can be in enabled state.
        //      * No action can be in an in-progress state (since binding resolution is needed for actions to
        //        be processed, no action processing can have happened yet)
        //
        // (2) From an existing InputActionState when a device has been added or removed.
        //     There is an InputActionState and the action setup (maps, actions, bindings, binding masks) has not changed. However,
        //     the set of devices usable with the action has changed (either the per-asset/map device list or the global
        //     list, if we're using it).
        //     NOTE:
        //      * Actions can be in enabled state.
        //      * Actions *can* be in an in-progress state.
        //        IF the control currently driving the action is on a device that is no longer usable with the action, the
        //        action is CANCELLED. OTHERWISE, the action will be left as is and keep being in progress from its active control.
        //      * A device CONFIGURATION change will NOT go down this path (e.g. changing the Keyboard layout). This is because
        //        any binding path involving display names may now resolve to different controls -- which may impact currently
        //        active controls of in-progress actions.
        //      * A change in the USAGES of a device will NOT go down this path either. This is for the same reason -- i.e. an
        //        active control may no longer match the binding path it matched before. If, for example, we switch the left-hand
        //        and right-hand roles of two controllers, will will go down path (3) and not (2).
        //
        // (3) From an existing InputActionState on any other change not covered before.
        //     There is an InputActionState and the action setup (maps, actions, bindings, binding masks) may have changed. Also,
        //     any change may have happened in the set of usable devices and targeted controls. This includes binding overrides
        //     having been applied.
        //     NOTE:
        //      * Action can be in enabled state.
        //      * Actions *can* be in an in-progress state.
        //        Any such action will be CANCELLED as part of the re-resolution process.
        //
        // Both (1) and (3) are considered a "full resolve". (2) is not.
        /// 
        /// Resolve all bindings to their controls and also add any action interactions
        /// from the bindings.
        /// 
        /// 
        /// This is the core method of action binding resolution. All binding resolution goes through here.
        ///
        /// The best way is for binding resolution to happen once for each action map at the beginning of the game
        /// and to then enable and disable the maps as needed. However, the system will also re-resolve
        /// bindings if the control setup in the system changes (i.e. if devices are added or removed
        /// or if layouts in the system are changed).
        ///
        /// Bindings can be re-resolved while actions are enabled. This happens changing device or binding
        /// masks on action maps or assets (, , ,
        /// , ). Doing so will
        /// not affect the enable state of actions and, as much as possible, will try to take current
        /// action states across.
        /// 
        internal void ResolveBindings()
        {
            // Make sure that if we trigger callbacks as part of disabling and re-enabling actions,
            // we don't trigger a re-resolve while we're already resolving bindings.
            using (InputActionRebindingExtensions.DeferBindingResolution())
            {
                // In case we have actions that are currently enabled, we temporarily retain the
                // UnmanagedMemory of our InputActionState so that we can sync action states after
                // we have re-resolved bindings.
                var oldMemory = new InputActionState.UnmanagedMemory();
                try
                {
                    OneOrMore> actionMaps;
                    // Start resolving.
                    var resolver = new InputBindingResolver();
                    // If we're part of an asset, we share state and thus binding resolution with
                    // all maps in the asset.
                    var needFullResolve = m_State == null;
                    if (m_Asset != null)
                    {
                        actionMaps = m_Asset.actionMaps;
                        Debug.Assert(actionMaps.Count > 0, "Asset referred to by action map does not have action maps");
                        // If there's a binding mask set on the asset, apply it.
                        resolver.bindingMask = m_Asset.m_BindingMask;
                        foreach (var map in actionMaps)
                        {
                            needFullResolve |= map.bindingResolutionNeedsFullReResolve;
                            map.needToResolveBindings = false;
                            map.bindingResolutionNeedsFullReResolve = false;
                            map.controlsForEachActionInitialized = false;
                        }
                    }
                    else
                    {
                        // Standalone action map (possibly a hidden one created for a singleton action).
                        // Gets its own private state.
                        actionMaps = this;
                        needFullResolve |= bindingResolutionNeedsFullReResolve;
                        needToResolveBindings = false;
                        bindingResolutionNeedsFullReResolve = false;
                        controlsForEachActionInitialized = false;
                    }
                    // If we already have a state, re-use the arrays we have already allocated.
                    // NOTE: We will install the arrays on the very same InputActionState instance below. In the
                    //       case where we didn't have to grow the arrays, we should end up with zero GC allocations
                    //       here.
                    var hasEnabledActions = false;
                    InputControlList activeControls = default;
                    if (m_State != null)
                    {
                        // Grab a clone of the current memory. We clone because disabling all the actions
                        // in the map will alter the memory state and we want the state before we start
                        // touching it.
                        oldMemory = m_State.memory.Clone();
                        m_State.PrepareForBindingReResolution(needFullResolve, ref activeControls, ref hasEnabledActions);
                        // Reuse the arrays we have so that we can avoid managed memory allocations, if possible.
                        resolver.StartWithPreviousResolve(m_State, isFullResolve: needFullResolve);
                        // Throw away old memory.
                        m_State.memory.Dispose();
                    }
                    // Resolve all maps in the asset.
                    foreach (var map in actionMaps)
                        resolver.AddActionMap(map);
                    // Install state.
                    if (m_State == null)
                    {
                        m_State = new InputActionState();
                        m_State.Initialize(resolver);
                    }
                    else
                    {
                        m_State.ClaimDataFrom(resolver);
                    }
                    if (m_Asset != null)
                    {
                        foreach (var map in actionMaps)
                            map.m_State = m_State;
                        m_Asset.m_SharedStateForAllMaps = m_State;
                    }
                    m_State.FinishBindingResolution(hasEnabledActions, oldMemory, activeControls, isFullResolve: needFullResolve);
                }
                finally
                {
                    oldMemory.Dispose();
                }
            }
        }
        /// 
        public int FindBinding(InputBinding mask, out InputAction action)
        {
            var index = FindBindingRelativeToMap(mask);
            if (index == -1)
            {
                action = null;
                return -1;
            }
            action = m_SingletonAction ?? FindAction(bindings[index].action);
            return action.BindingIndexOnMapToBindingIndexOnAction(index);
        }
        /// 
        /// Find the index of the first binding that matches the given mask.
        /// 
        /// A binding. See  for details.
        /// Index into  of  of the binding
        /// that matches . If no binding matches, will return -1.
        /// 
        /// For details about matching bindings by a mask, see .
        ///
        /// 
        /// 
        /// var index = playerInput.actions.FindBindingRelativeToMap(
        ///     new InputBinding { path = "<Gamepad>/buttonSouth" });
        ///
        /// if (index != -1)
        ///     Debug.Log($"Found binding with index {index}");
        /// 
        /// 
        /// 
        /// 
        /// 
        internal int FindBindingRelativeToMap(InputBinding mask)
        {
            var bindings = m_Bindings;
            var bindingsCount = bindings.LengthSafe();
            for (var i = 0; i < bindingsCount; ++i)
            {
                ref var binding = ref bindings[i];
                if (mask.Matches(ref binding))
                    return i;
            }
            return -1;
        }
        #region Serialization
        ////REVIEW: when GetParameter/SetParameter is coming, should these also be considered part of binding override data?
        [Serializable]
        internal struct BindingOverrideListJson
        {
            public List bindings;
        }
        [Serializable]
        internal struct BindingOverrideJson
        {
            // We save both the "map/action" path of the action as well as the binding ID.
            // This gives us two avenues into finding our target binding to apply the override
            // to.
            public string action;
            public string id;
            public string path;
            public string interactions;
            public string processors;
            public static BindingOverrideJson FromBinding(InputBinding binding)
            {
                return new BindingOverrideJson
                {
                    action = binding.action,
                    id = binding.m_Id,
                    path = binding.overridePath,
                    interactions = binding.overrideInteractions,
                    processors = binding.overrideProcessors,
                };
            }
        }
        // Action maps are serialized in two different ways. For storage as imported assets in Unity's Library/ folder
        // and in player data and asset bundles as well as for surviving domain reloads, InputActionMaps are serialized
        // directly by Unity. For storage as source data in user projects, InputActionMaps are serialized indirectly
        // as JSON by setting up a separate set of structs that are then read and written using Unity's JSON serializer.
        [Serializable]
        internal struct BindingJson
        {
            public string name;
            public string id;
            public string path;
            public string interactions;
            public string processors;
            public string groups;
            public string action;
            public bool isComposite;
            public bool isPartOfComposite;
            public InputBinding ToBinding()
            {
                return new InputBinding
                {
                    name = string.IsNullOrEmpty(name) ? null : name,
                    m_Id = string.IsNullOrEmpty(id) ? null : id,
                    path = path,
                    action = string.IsNullOrEmpty(action) ? null : action,
                    interactions = string.IsNullOrEmpty(interactions) ? null : interactions,
                    processors = string.IsNullOrEmpty(processors) ? null : processors,
                    groups = string.IsNullOrEmpty(groups) ? null : groups,
                    isComposite = isComposite,
                    isPartOfComposite = isPartOfComposite,
                };
            }
            public static BindingJson FromBinding(ref InputBinding binding)
            {
                return new BindingJson
                {
                    name = binding.name,
                    id = binding.m_Id,
                    path = binding.path,
                    action = binding.action,
                    interactions = binding.interactions,
                    processors = binding.processors,
                    groups = binding.groups,
                    isComposite = binding.isComposite,
                    isPartOfComposite = binding.isPartOfComposite,
                };
            }
        }
        // Backwards-compatible read format.
        [Serializable]
        internal struct ReadActionJson
        {
            public string name;
            public string type;
            public string id;
            public string expectedControlType;
            public string expectedControlLayout;
            public string processors;
            public string interactions;
            public bool passThrough;
            public bool initialStateCheck;
            // Bindings can either be on the action itself (in which case the action name
            // for each binding is implied) or listed separately in the action file.
            public BindingJson[] bindings;
            public InputAction ToAction(string actionName = null)
            {
                // FormerlySerializedAs doesn't seem to work as expected so manually
                // handling the rename here.
                if (!string.IsNullOrEmpty(expectedControlLayout))
                    expectedControlType = expectedControlLayout;
                // Determine type.
                InputActionType actionType = default;
                if (!string.IsNullOrEmpty(type))
                    actionType = (InputActionType)Enum.Parse(typeof(InputActionType), type, true);
                else
                {
                    // Old format that doesn't have type. Try to infer from settings.
                    if (passThrough)
                        actionType = InputActionType.PassThrough;
                    else if (initialStateCheck)
                        actionType = InputActionType.Value;
                    else if (!string.IsNullOrEmpty(expectedControlType) &&
                             (expectedControlType == "Button" || expectedControlType == "Key"))
                        actionType = InputActionType.Button;
                }
                return new InputAction(actionName ?? name, actionType)
                {
                    m_Id = string.IsNullOrEmpty(id) ? null : id,
                    m_ExpectedControlType = !string.IsNullOrEmpty(expectedControlType)
                        ? expectedControlType
                        : null,
                    m_Processors = processors,
                    m_Interactions = interactions,
                    wantsInitialStateCheck = initialStateCheck,
                };
            }
        }
        [Serializable]
        internal struct WriteActionJson
        {
            public string name;
            public string type;
            public string id;
            public string expectedControlType;
            public string processors;
            public string interactions;
            public bool initialStateCheck;
            public static WriteActionJson FromAction(InputAction action)
            {
                return new WriteActionJson
                {
                    name = action.m_Name,
                    type = action.m_Type.ToString(),
                    id = action.m_Id,
                    expectedControlType = action.m_ExpectedControlType,
                    processors = action.processors,
                    interactions = action.interactions,
                    initialStateCheck = action.wantsInitialStateCheck,
                };
            }
        }
        [Serializable]
        internal struct ReadMapJson
        {
            public string name;
            public string id;
            public ReadActionJson[] actions;
            public BindingJson[] bindings;
        }
        [Serializable]
        internal struct WriteMapJson
        {
            public string name;
            public string id;
            public WriteActionJson[] actions;
            public BindingJson[] bindings;
            public static WriteMapJson FromMap(InputActionMap map)
            {
                WriteActionJson[] jsonActions = null;
                BindingJson[] jsonBindings = null;
                var actions = map.m_Actions;
                if (actions != null)
                {
                    var actionCount = actions.Length;
                    jsonActions = new WriteActionJson[actionCount];
                    for (var i = 0; i < actionCount; ++i)
                        jsonActions[i] = WriteActionJson.FromAction(actions[i]);
                }
                var bindings = map.m_Bindings;
                if (bindings != null)
                {
                    var bindingCount = bindings.Length;
                    jsonBindings = new BindingJson[bindingCount];
                    for (var i = 0; i < bindingCount; ++i)
                        jsonBindings[i] = BindingJson.FromBinding(ref bindings[i]);
                }
                return new WriteMapJson
                {
                    name = map.name,
                    id = map.id.ToString(),
                    actions = jsonActions,
                    bindings = jsonBindings,
                };
            }
        }
        // We write JSON in a less flexible format than we allow to be read. JSON files
        // we read can just be flat lists of actions with the map name being contained in
        // the action name and containing their own bindings directly. JSON files we write
        // go map by map and separate bindings and actions.
        [Serializable]
        internal struct WriteFileJson
        {
            public WriteMapJson[] maps;
            public static WriteFileJson FromMap(InputActionMap map)
            {
                return new WriteFileJson
                {
                    maps = new[] {WriteMapJson.FromMap(map)}
                };
            }
            public static WriteFileJson FromMaps(IEnumerable maps)
            {
                var mapCount = maps.Count();
                if (mapCount == 0)
                    return new WriteFileJson();
                var mapsJson = new WriteMapJson[mapCount];
                var index = 0;
                foreach (var map in maps)
                    mapsJson[index++] = WriteMapJson.FromMap(map);
                return new WriteFileJson {maps = mapsJson};
            }
        }
        // A JSON representation of one or more sets of actions.
        // Contains a list of actions. Each action may specify the set it belongs to
        // as part of its name ("set/action").
        [Serializable]
        internal struct ReadFileJson
        {
            public ReadActionJson[] actions;
            public ReadMapJson[] maps;
            public InputActionMap[] ToMaps()
            {
                var mapList = new List();
                var actionLists = new List>();
                var bindingLists = new List>();
                // Process actions listed at toplevel.
                var actionCount = actions?.Length ?? 0;
                for (var i = 0; i < actionCount; ++i)
                {
                    var jsonAction = actions[i];
                    if (string.IsNullOrEmpty(jsonAction.name))
                        throw new InvalidOperationException($"Action number {i + 1} has no name");
                    ////REVIEW: make sure all action names are unique?
                    // Determine name of action map.
                    string mapName = null;
                    var actionName = jsonAction.name;
                    var indexOfFirstSlash = actionName.IndexOf('/');
                    if (indexOfFirstSlash != -1)
                    {
                        mapName = actionName.Substring(0, indexOfFirstSlash);
                        actionName = actionName.Substring(indexOfFirstSlash + 1);
                        if (string.IsNullOrEmpty(actionName))
                            throw new InvalidOperationException(
                                $"Invalid action name '{jsonAction.name}' (missing action name after '/')");
                    }
                    // Try to find existing map.
                    InputActionMap map = null;
                    var mapIndex = 0;
                    for (; mapIndex < mapList.Count; ++mapIndex)
                    {
                        if (string.Compare(mapList[mapIndex].name, mapName, StringComparison.InvariantCultureIgnoreCase) == 0)
                        {
                            map = mapList[mapIndex];
                            break;
                        }
                    }
                    // Create new map if it's the first action in the map.
                    if (map == null)
                    {
                        // NOTE: No map IDs supported on this path.
                        map = new InputActionMap(mapName);
                        mapIndex = mapList.Count;
                        mapList.Add(map);
                        actionLists.Add(new List());
                        bindingLists.Add(new List());
                    }
                    // Create action.
                    var action = jsonAction.ToAction(actionName);
                    actionLists[mapIndex].Add(action);
                    // Add bindings.
                    if (jsonAction.bindings != null)
                    {
                        var bindingsForMap = bindingLists[mapIndex];
                        for (var n = 0; n < jsonAction.bindings.Length; ++n)
                        {
                            var jsonBinding = jsonAction.bindings[n];
                            var binding = jsonBinding.ToBinding();
                            binding.action = action.m_Name;
                            bindingsForMap.Add(binding);
                        }
                    }
                }
                // Process maps.
                var mapCount = maps?.Length ?? 0;
                for (var i = 0; i < mapCount; ++i)
                {
                    var jsonMap = maps[i];
                    var mapName = jsonMap.name;
                    if (string.IsNullOrEmpty(mapName))
                        throw new InvalidOperationException($"Map number {i + 1} has no name");
                    // Try to find existing map.
                    InputActionMap map = null;
                    var mapIndex = 0;
                    for (; mapIndex < mapList.Count; ++mapIndex)
                    {
                        if (string.Compare(mapList[mapIndex].name, mapName, StringComparison.InvariantCultureIgnoreCase) == 0)
                        {
                            map = mapList[mapIndex];
                            break;
                        }
                    }
                    // Create new map if we haven't seen it before.
                    if (map == null)
                    {
                        map = new InputActionMap(mapName)
                        {
                            m_Id = string.IsNullOrEmpty(jsonMap.id) ? null : jsonMap.id
                        };
                        mapIndex = mapList.Count;
                        mapList.Add(map);
                        actionLists.Add(new List());
                        bindingLists.Add(new List());
                    }
                    // Process actions in map.
                    var actionCountInMap = jsonMap.actions?.Length ?? 0;
                    for (var n = 0; n < actionCountInMap; ++n)
                    {
                        var jsonAction = jsonMap.actions[n];
                        if (string.IsNullOrEmpty(jsonAction.name))
                            throw new InvalidOperationException($"Action number {i + 1} in map '{mapName}' has no name");
                        // Create action.
                        var action = jsonAction.ToAction();
                        actionLists[mapIndex].Add(action);
                        // Add bindings.
                        if (jsonAction.bindings != null)
                        {
                            var bindingList = bindingLists[mapIndex];
                            for (var k = 0; k < jsonAction.bindings.Length; ++k)
                            {
                                var jsonBinding = jsonAction.bindings[k];
                                var binding = jsonBinding.ToBinding();
                                binding.action = action.m_Name;
                                bindingList.Add(binding);
                            }
                        }
                    }
                    // Process bindings in map.
                    var bindingCountInMap = jsonMap.bindings?.Length ?? 0;
                    var bindingsForMap = bindingLists[mapIndex];
                    for (var n = 0; n < bindingCountInMap; ++n)
                    {
                        var jsonBinding = jsonMap.bindings[n];
                        var binding = jsonBinding.ToBinding();
                        bindingsForMap.Add(binding);
                    }
                }
                // Finalize arrays.
                for (var i = 0; i < mapList.Count; ++i)
                {
                    var map = mapList[i];
                    var actionArray = actionLists[i].ToArray();
                    var bindingArray = bindingLists[i].ToArray();
                    map.m_Actions = actionArray;
                    map.m_Bindings = bindingArray;
                    for (var n = 0; n < actionArray.Length; ++n)
                    {
                        var action = actionArray[n];
                        action.m_ActionMap = map;
                    }
                }
                return mapList.ToArray();
            }
        }
        /// 
        /// Load one or more action maps from JSON.
        /// 
        /// JSON representation of the action maps. Can be empty.
        ///  is null.
        /// The array of action maps (may be empty) read from the given JSON string. Will not be
        /// null.
        /// 
        /// Note that the format used by this method is different than what you
        /// get if you call JsonUtility.ToJson on an InputActionMap instance. In other
        /// words, the JSON format is not identical to the Unity serialized object representation
        /// of the asset.
        ///
        /// 
        /// 
        /// var maps = InputActionMap.FromJson(@"
        ///     {
        ///         ""maps"" : [
        ///             {
        ///                 ""name"" : ""Gameplay"",
        ///                 ""actions"" : [
        ///                     { ""name"" : ""fire"", ""type"" : ""button"" }
        ///                 ],
        ///                 ""bindings"" : [
        ///                     { ""path"" : ""<Gamepad>/leftTrigger"", ""action"" : ""fire"" }
        ///                 ],
        ///             }
        ///         ]
        ///     }
        /// ");
        /// 
        /// 
        /// 
        /// 
        /// 
        public static InputActionMap[] FromJson(string json)
        {
            if (json == null)
                throw new ArgumentNullException(nameof(json));
            var fileJson = JsonUtility.FromJson(json);
            return fileJson.ToMaps();
        }
        /// 
        /// Convert a set of action maps to JSON format.
        /// 
        /// List of action maps to serialize.
        ///  is null.
        /// JSON representation of the given action maps.
        /// 
        /// The result of this method can be loaded with .
        ///
        /// Note that the format used by this method is different than what you
        /// get if you call JsonUtility.ToJson on an InputActionMap instance. In other
        /// words, the JSON format is not identical to the Unity serialized object representation
        /// of the asset.
        /// 
        /// 
        public static string ToJson(IEnumerable maps)
        {
            if (maps == null)
                throw new ArgumentNullException(nameof(maps));
            var fileJson = WriteFileJson.FromMaps(maps);
            return JsonUtility.ToJson(fileJson, true);
        }
        /// 
        /// Convert the action map to JSON format.
        /// 
        /// A JSON representation of the action map.
        /// 
        /// The result of this method can be loaded with .
        ///
        /// Note that the format used by this method is different than what you
        /// get if you call JsonUtility.ToJson on an InputActionMap instance. In other
        /// words, the JSON format is not identical to the Unity serialized object representation
        /// of the asset.
        /// 
        public string ToJson()
        {
            var fileJson = WriteFileJson.FromMap(this);
            return JsonUtility.ToJson(fileJson, true);
        }
        /// 
        /// Called by Unity before the action map is serialized using Unity's
        /// serialization system.
        /// 
        public void OnBeforeSerialize()
        {
        }
        /// 
        /// Called by Unity after the action map has been deserialized using Unity's
        /// serialization system.
        /// 
        public void OnAfterDeserialize()
        {
            m_State = null;
            m_MapIndexInState = InputActionState.kInvalidIndex;
            // Restore references of actions linking back to us.
            if (m_Actions != null)
            {
                var actionCount = m_Actions.Length;
                for (var i = 0; i < actionCount; ++i)
                    m_Actions[i].m_ActionMap = this;
            }
            // Make sure we don't retain any cached per-action data when using serialization
            // to doctor around in action map configurations in the editor.
            ClearCachedActionData();
            ClearActionLookupTable();
        }
        #endregion
    }
}