using System;
using System.Collections.Generic;
using System.Reflection;
using Unity.Collections;
using UnityEngine.InputSystem.Utilities;

////TODO: reuse interaction, processor, and composite instances from prior resolves

namespace UnityEngine.InputSystem
{
    /// <summary>
    /// Heart of the binding resolution machinery. Consumes lists of bindings
    /// and spits out out a list of resolved bindings together with their needed
    /// execution state.
    /// </summary>
    /// <remarks>
    /// One or more <see cref="InputActionMap">action maps</see> can be added to the same
    /// resolver. The result is a combination of the binding state of all maps.
    ///
    /// The data set up by a resolver is for consumption by <see cref="InputActionState"/>.
    /// Essentially, InputBindingResolver does all the wiring and <see cref="InputActionState"/>
    /// does all the actual execution based on the resulting data.
    /// </remarks>
    /// <seealso cref="InputActionState.Initialize"/>
    internal struct InputBindingResolver : IDisposable
    {
        public int totalProcessorCount;
        public int totalCompositeCount;
        public int totalInteractionCount;
        public int totalMapCount => memory.mapCount;
        public int totalActionCount => memory.actionCount;
        public int totalBindingCount => memory.bindingCount;
        public int totalControlCount => memory.controlCount;

        public InputActionMap[] maps;
        public InputControl[] controls;
        public InputActionState.UnmanagedMemory memory;
        public IInputInteraction[] interactions;
        public InputProcessor[] processors;
        public InputBindingComposite[] composites;

        /// <summary>
        /// Binding mask used to globally mask out bindings.
        /// </summary>
        /// <remarks>
        /// This is empty by default.
        ///
        /// The bindings of each map will be <see cref="InputBinding.Matches">matched</see> against this
        /// binding. Any bindings that don't match will get skipped and not resolved to controls.
        ///
        /// Note that regardless of whether a binding will be resolved to controls or not, it will get
        /// an entry in <see cref="memory"/>. Otherwise we would have to have a more complicated
        /// mapping from <see cref="InputActionMap.bindings"/> to a binding state in <see cref="memory"/>.
        /// </remarks>
        public InputBinding? bindingMask;

        private bool m_IsControlOnlyResolve;

        /// <summary>
        /// Release native memory held by the resolver.
        /// </summary>
        public void Dispose()
        {
            memory.Dispose();
        }

        /// <summary>
        /// Steal the already allocated arrays from the given state.
        /// </summary>
        /// <param name="state">Action map state that was previously created.</param>
        /// <param name="isFullResolve">If false, the only thing that is allowed to change in the re-resolution
        /// is the list of controls. In other words, devices may have been added or removed but otherwise the configuration
        /// is exactly the same as in the last resolve. If true, anything may have changed and the resolver will only reuse
        /// allocations but not contents.</param>
        public void StartWithPreviousResolve(InputActionState state, bool isFullResolve)
        {
            Debug.Assert(state != null, "Received null state");
            Debug.Assert(!state.isProcessingControlStateChange,
                "Cannot re-resolve bindings for an InputActionState that is currently executing an action callback; binding resolution must be deferred to until after the callback has completed");

            m_IsControlOnlyResolve = !isFullResolve;

            maps = state.maps;
            interactions = state.interactions;
            processors = state.processors;
            composites = state.composites;
            controls = state.controls;

            // Clear the arrays so that we don't leave references around.
            if (isFullResolve)
            {
                if (maps != null)
                    Array.Clear(maps, 0, state.totalMapCount);
                if (interactions != null)
                    Array.Clear(interactions, 0, state.totalInteractionCount);
                if (processors != null)
                    Array.Clear(processors, 0, state.totalProcessorCount);
                if (composites != null)
                    Array.Clear(composites, 0, state.totalCompositeCount);
            }
            if (controls != null) // Always clear this one as every resolve will change it.
                Array.Clear(controls, 0, state.totalControlCount);

            // Null out the arrays on the state so that there is no strange bugs with
            // the state reading from arrays that no longer belong to it.
            state.maps = null;
            state.interactions = null;
            state.processors = null;
            state.composites = null;
            state.controls = null;
        }

        /// <summary>
        /// Resolve and add all bindings and actions from the given map.
        /// </summary>
        /// <param name="actionMap"></param>
        /// <remarks>
        /// This is where all binding resolution happens for actions. The method walks through the binding array
        /// in <paramref name="actionMap"/> and adds any controls, interactions, processors, and composites as it goes.
        /// </remarks>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1809:AvoidExcessiveLocals", Justification = "TODO: Refactor later.")]
        public unsafe void AddActionMap(InputActionMap actionMap)
        {
            Debug.Assert(actionMap != null, "Received null map");

            InputSystem.EnsureInitialized();

            var actionsInThisMap = actionMap.m_Actions;
            var bindingsInThisMap = actionMap.m_Bindings;
            var bindingCountInThisMap = bindingsInThisMap?.Length ?? 0;
            var actionCountInThisMap = actionsInThisMap?.Length ?? 0;
            var mapIndex = totalMapCount;

            // Keep track of indices for this map.
            var actionStartIndex = totalActionCount;
            var bindingStartIndex = totalBindingCount;
            var controlStartIndex = totalControlCount;
            var interactionStartIndex = totalInteractionCount;
            var processorStartIndex = totalProcessorCount;
            var compositeStartIndex = totalCompositeCount;

            // Allocate an initial block of memory. We probably will have to re-allocate once
            // at the end to accommodate interactions and controls added from the map.
            var newMemory = new InputActionState.UnmanagedMemory();
            newMemory.Allocate(
                mapCount: totalMapCount + 1,
                actionCount: totalActionCount + actionCountInThisMap,
                bindingCount: totalBindingCount + bindingCountInThisMap,
                // We reallocate for the following once we know the final count.
                interactionCount: totalInteractionCount,
                compositeCount: totalCompositeCount,
                controlCount: totalControlCount);
            if (memory.isAllocated)
                newMemory.CopyDataFrom(memory);

            ////TODO: make sure composite objects get all the bindings they need
            ////TODO: handle case where we have bindings resolving to the same control
            ////      (not so clear cut what to do there; each binding may have a different interaction setup, for example)
            var currentCompositeBindingIndex = InputActionState.kInvalidIndex;
            var currentCompositeIndex = InputActionState.kInvalidIndex;
            var currentCompositePartCount = 0;
            var currentCompositeActionIndexInMap = InputActionState.kInvalidIndex;
            InputAction currentCompositeAction = null;
            var bindingMaskOnThisMap = actionMap.m_BindingMask;
            var devicesForThisMap = actionMap.devices;
            var isSingletonAction = actionMap.m_SingletonAction != null;

            // Can't use `using` as we need to use it with `ref`.
            var resolvedControls = new InputControlList<InputControl>(Allocator.Temp);

            // We gather all controls in temporary memory and then move them over into newMemory once
            // we're done resolving.
            try
            {
                for (var n = 0; n < bindingCountInThisMap; ++n)
                {
                    var bindingStatesPtr = newMemory.bindingStates;
                    ref var unresolvedBinding = ref bindingsInThisMap[n];
                    var bindingIndex = bindingStartIndex + n;
                    var isComposite = unresolvedBinding.isComposite;
                    var isPartOfComposite = !isComposite && unresolvedBinding.isPartOfComposite;
                    var bindingState = &bindingStatesPtr[bindingIndex];

                    try
                    {
                        ////TODO: if it's a composite, check if any of the children matches our binding masks (if any) and skip composite if none do

                        var firstControlIndex = 0; // numControls dictates whether this is a valid index or not.
                        var firstInteractionIndex = InputActionState.kInvalidIndex;
                        var firstProcessorIndex = InputActionState.kInvalidIndex;
                        var actionIndexForBinding = InputActionState.kInvalidIndex;
                        var partIndex = InputActionState.kInvalidIndex;

                        var numControls = 0;
                        var numInteractions = 0;
                        var numProcessors = 0;

                        // Make sure that if it's part of a composite, we are actually part of a composite.
                        if (isPartOfComposite && currentCompositeBindingIndex == InputActionState.kInvalidIndex)
                            throw new InvalidOperationException(
                                $"Binding '{unresolvedBinding}' is marked as being part of a composite but the preceding binding is not a composite");

                        // Try to find action.
                        //
                        // NOTE: We ignore actions on bindings that are part of composites. We only allow
                        //       actions to be triggered from the composite itself.
                        var actionIndexInMap = InputActionState.kInvalidIndex;
                        var actionName = unresolvedBinding.action;
                        InputAction action = null;
                        if (!isPartOfComposite)
                        {
                            if (isSingletonAction)
                            {
                                // Singleton actions always ignore names.
                                actionIndexInMap = 0;
                            }
                            else if (!string.IsNullOrEmpty(actionName))
                            {
                                ////REVIEW: should we fail here if we don't manage to find the action
                                actionIndexInMap = actionMap.FindActionIndex(actionName);
                            }

                            if (actionIndexInMap != InputActionState.kInvalidIndex)
                                action = actionsInThisMap[actionIndexInMap];
                        }
                        else
                        {
                            actionIndexInMap = currentCompositeActionIndexInMap;
                            action = currentCompositeAction;
                        }

                        // If it's a composite, start a chain.
                        if (isComposite)
                        {
                            currentCompositeBindingIndex = bindingIndex;
                            currentCompositeAction = action;
                            currentCompositeActionIndexInMap = actionIndexInMap;
                        }

                        // Determine if the binding is disabled.
                        // Disabled if path is empty.
                        var path = unresolvedBinding.effectivePath;
                        var bindingIsDisabled = string.IsNullOrEmpty(path)

                            // Also, if we can't find the action to trigger for the binding, we just go and disable
                            // the binding.
                            || action == null

                            // Also, disabled if binding doesn't match with our binding mask (might be empty).
                            || (!isComposite && bindingMask != null &&
                                !bindingMask.Value.Matches(ref unresolvedBinding,
                                    InputBinding.MatchOptions.EmptyGroupMatchesAny))

                            // Also, disabled if binding doesn't match the binding mask on the map (might be empty).
                            || (!isComposite && bindingMaskOnThisMap != null &&
                                !bindingMaskOnThisMap.Value.Matches(ref unresolvedBinding,
                                    InputBinding.MatchOptions.EmptyGroupMatchesAny))

                            // Finally, also disabled if binding doesn't match the binding mask on the action (might be empty).
                            || (!isComposite && action?.m_BindingMask != null &&
                                !action.m_BindingMask.Value.Matches(ref unresolvedBinding,
                                    InputBinding.MatchOptions.EmptyGroupMatchesAny));

                        // If the binding isn't disabled, look up controls now. We do this first as we may still disable the
                        // binding if it doesn't resolve to any controls or resolves only to controls already bound to by
                        // other bindings.
                        //
                        // NOTE: We continuously add controls here to `resolvedControls`. Once we've completed our
                        //       pass over the bindings in the map, `resolvedControls` will have all the controls for
                        //       the current map.
                        if (!bindingIsDisabled && !isComposite)
                        {
                            firstControlIndex = memory.controlCount + resolvedControls.Count;
                            if (devicesForThisMap != null)
                            {
                                // Search in devices for only this map.
                                var list = devicesForThisMap.Value;
                                for (var i = 0; i < list.Count; ++i)
                                {
                                    var device = list[i];
                                    if (!device.added)
                                        continue; // Skip devices that have been removed.
                                    numControls += InputControlPath.TryFindControls(device, path, 0, ref resolvedControls);
                                }
                            }
                            else
                            {
                                // Search globally.
                                numControls = InputSystem.FindControls(path, ref resolvedControls);
                            }
                        }

                        // If the binding isn't disabled, resolve its controls, processors, and interactions.
                        if (!bindingIsDisabled)
                        {
                            // NOTE: When isFullResolve==false, it is *imperative* that we do count processor and interaction
                            //       counts here come out exactly the same as in the previous full resolve.

                            // Instantiate processors.
                            var processorString = unresolvedBinding.effectiveProcessors;
                            if (!string.IsNullOrEmpty(processorString))
                            {
                                // Add processors from binding.
                                firstProcessorIndex = InstantiateWithParameters(InputProcessor.s_Processors, processorString,
                                    ref processors, ref totalProcessorCount, actionMap, ref unresolvedBinding);
                                if (firstProcessorIndex != InputActionState.kInvalidIndex)
                                    numProcessors = totalProcessorCount - firstProcessorIndex;
                            }
                            if (!string.IsNullOrEmpty(action.m_Processors))
                            {
                                // Add processors from action.
                                var index = InstantiateWithParameters(InputProcessor.s_Processors, action.m_Processors, ref processors,
                                    ref totalProcessorCount, actionMap, ref unresolvedBinding);
                                if (index != InputActionState.kInvalidIndex)
                                {
                                    if (firstProcessorIndex == InputActionState.kInvalidIndex)
                                        firstProcessorIndex = index;
                                    numProcessors += totalProcessorCount - index;
                                }
                            }

                            // Instantiate interactions.
                            if (isPartOfComposite)
                            {
                                // Composite's part use composite interactions
                                if (currentCompositeBindingIndex != InputActionState.kInvalidIndex)
                                {
                                    firstInteractionIndex = bindingStatesPtr[currentCompositeBindingIndex].interactionStartIndex;
                                    numInteractions = bindingStatesPtr[currentCompositeBindingIndex].interactionCount;
                                }
                            }
                            else
                            {
                                var interactionString = unresolvedBinding.effectiveInteractions;
                                if (!string.IsNullOrEmpty(interactionString))
                                {
                                    // Add interactions from binding.
                                    firstInteractionIndex = InstantiateWithParameters(InputInteraction.s_Interactions, interactionString,
                                        ref interactions, ref totalInteractionCount, actionMap, ref unresolvedBinding);
                                    if (firstInteractionIndex != InputActionState.kInvalidIndex)
                                        numInteractions = totalInteractionCount - firstInteractionIndex;
                                }
                                if (!string.IsNullOrEmpty(action.m_Interactions))
                                {
                                    // Add interactions from action.
                                    var index = InstantiateWithParameters(InputInteraction.s_Interactions, action.m_Interactions,
                                        ref interactions, ref totalInteractionCount, actionMap, ref unresolvedBinding);
                                    if (index != InputActionState.kInvalidIndex)
                                    {
                                        if (firstInteractionIndex == InputActionState.kInvalidIndex)
                                            firstInteractionIndex = index;
                                        numInteractions += totalInteractionCount - index;
                                    }
                                }
                            }

                            // If it's the start of a composite chain, create the composite.
                            if (isComposite)
                            {
                                // The composite binding entry itself does not resolve to any controls.
                                // It creates a composite binding object which is then populated from
                                // subsequent bindings.

                                // Instantiate. For composites, the path is the name of the composite.
                                var composite = InstantiateBindingComposite(ref unresolvedBinding, actionMap);
                                currentCompositeIndex =
                                    ArrayHelpers.AppendWithCapacity(ref composites, ref totalCompositeCount, composite);

                                // Record where the controls for parts of the composite start.
                                firstControlIndex = memory.controlCount + resolvedControls.Count;
                            }
                            else
                            {
                                // If we've reached the end of a composite chain, finish
                                // off the current composite.
                                if (!isPartOfComposite && currentCompositeBindingIndex != InputActionState.kInvalidIndex)
                                {
                                    currentCompositePartCount = 0;
                                    currentCompositeBindingIndex = InputActionState.kInvalidIndex;
                                    currentCompositeIndex = InputActionState.kInvalidIndex;
                                    currentCompositeAction = null;
                                    currentCompositeActionIndexInMap = InputActionState.kInvalidIndex;
                                }
                            }
                        }

                        // If the binding is part of a composite, pass the resolved controls
                        // on to the composite.
                        if (isPartOfComposite && currentCompositeBindingIndex != InputActionState.kInvalidIndex && numControls > 0)
                        {
                            // Make sure the binding is named. The name determines what in the composite
                            // to bind to.
                            if (string.IsNullOrEmpty(unresolvedBinding.name))
                                throw new InvalidOperationException(
                                    $"Binding '{unresolvedBinding}' that is part of composite '{composites[currentCompositeIndex]}' is missing a name");

                            // Assign an index to the current part of the composite which
                            // can be used by the composite to read input from this part.
                            partIndex = AssignCompositePartIndex(composites[currentCompositeIndex], unresolvedBinding.name,
                                ref currentCompositePartCount);

                            // Keep track of total number of controls bound in the composite.
                            bindingStatesPtr[currentCompositeBindingIndex].controlCount += numControls;

                            // Force action index on part binding to be same as that of composite.
                            actionIndexForBinding = bindingStatesPtr[currentCompositeBindingIndex].actionIndex;
                        }
                        else if (actionIndexInMap != InputActionState.kInvalidIndex)
                        {
                            actionIndexForBinding = actionStartIndex + actionIndexInMap;
                        }

                        // Store resolved binding.
                        *bindingState = new InputActionState.BindingState
                        {
                            controlStartIndex = firstControlIndex,
                            // For composites, this will be adjusted as we add each part.
                            controlCount = numControls,
                            interactionStartIndex = firstInteractionIndex,
                            interactionCount = numInteractions,
                            processorStartIndex = firstProcessorIndex,
                            processorCount = numProcessors,
                            isComposite = isComposite,
                            isPartOfComposite = unresolvedBinding.isPartOfComposite,
                            partIndex = partIndex,
                            actionIndex = actionIndexForBinding,
                            compositeOrCompositeBindingIndex = isComposite ? currentCompositeIndex : currentCompositeBindingIndex,
                            mapIndex = totalMapCount,
                            wantsInitialStateCheck = action?.wantsInitialStateCheck ?? false
                        };
                    }
                    catch (Exception exception)
                    {
                        Debug.LogError(
                            $"{exception.GetType().Name} while resolving binding '{unresolvedBinding}' in action map '{actionMap}'");
                        Debug.LogException(exception);

                        // Don't swallow exceptions that indicate something is wrong in the code rather than
                        // in the data.
                        if (exception.IsExceptionIndicatingBugInCode())
                            throw;
                    }
                }

                // Re-allocate memory to accommodate controls and interaction states. The count for those
                // we only know once we've completed all resolution.
                var controlCountInThisMap = resolvedControls.Count;
                var newTotalControlCount = memory.controlCount + controlCountInThisMap;
                if (newMemory.interactionCount != totalInteractionCount ||
                    newMemory.compositeCount != totalCompositeCount ||
                    newMemory.controlCount != newTotalControlCount)
                {
                    var finalMemory = new InputActionState.UnmanagedMemory();

                    finalMemory.Allocate(
                        mapCount: newMemory.mapCount,
                        actionCount: newMemory.actionCount,
                        bindingCount: newMemory.bindingCount,
                        controlCount: newTotalControlCount,
                        interactionCount: totalInteractionCount,
                        compositeCount: totalCompositeCount);

                    finalMemory.CopyDataFrom(newMemory);

                    newMemory.Dispose();
                    newMemory = finalMemory;
                }

                // Add controls to array.
                var controlCountInArray = memory.controlCount;
                ArrayHelpers.AppendListWithCapacity(ref controls, ref controlCountInArray, resolvedControls);
                Debug.Assert(controlCountInArray == newTotalControlCount,
                    "Control array should have combined count of old and new controls");

                // Set up control to binding index mapping.
                for (var i = 0; i < bindingCountInThisMap; ++i)
                {
                    var bindingStatesPtr = newMemory.bindingStates;
                    var bindingState = &bindingStatesPtr[bindingStartIndex + i];
                    var numControls = bindingState->controlCount;
                    var startIndex = bindingState->controlStartIndex;
                    for (var n = 0; n < numControls; ++n)
                        newMemory.controlIndexToBindingIndex[startIndex + n] = bindingStartIndex + i;
                }

                // Initialize initial interaction states.
                for (var i = memory.interactionCount; i < newMemory.interactionCount; ++i)
                {
                    ref var interactionState = ref newMemory.interactionStates[i];
                    interactionState.phase = InputActionPhase.Waiting;
                    interactionState.triggerControlIndex = InputActionState.kInvalidIndex;
                }

                // Initialize action data.
                var runningIndexInBindingIndices = memory.bindingCount;
                for (var i = 0; i < actionCountInThisMap; ++i)
                {
                    var action = actionsInThisMap[i];
                    var actionIndex = actionStartIndex + i;

                    // Correlate action with its trigger state.
                    action.m_ActionIndexInState = actionIndex;

                    Debug.Assert(runningIndexInBindingIndices < ushort.MaxValue, "Binding start index on action exceeds limit");
                    newMemory.actionBindingIndicesAndCounts[actionIndex * 2] = (ushort)runningIndexInBindingIndices;

                    // Collect bindings for action.
                    var firstBindingIndexForAction = -1;
                    var bindingCountForAction = 0;
                    var numPossibleConcurrentActuations = 0;

                    for (var n = 0; n < bindingCountInThisMap; ++n)
                    {
                        var bindingIndex = bindingStartIndex + n;
                        var bindingState = &newMemory.bindingStates[bindingIndex];
                        if (bindingState->actionIndex != actionIndex)
                            continue;
                        if (bindingState->isPartOfComposite)
                            continue;

                        Debug.Assert(bindingIndex <= ushort.MaxValue, "Binding index exceeds limit");
                        newMemory.actionBindingIndices[runningIndexInBindingIndices] = (ushort)bindingIndex;
                        ++runningIndexInBindingIndices;
                        ++bindingCountForAction;

                        if (firstBindingIndexForAction == -1)
                            firstBindingIndexForAction = bindingIndex;

                        // Keep track of how many concurrent actuations we may be seeing on the action so that
                        // we know whether we need to enable conflict resolution or not.
                        if (bindingState->isComposite)
                        {
                            // Composite binding. Actuates as a whole. Check if the composite has successfully
                            // resolved any controls. If so, it adds one possible actuation.
                            if (bindingState->controlCount > 0)
                                ++numPossibleConcurrentActuations;
                        }
                        else
                        {
                            // Normal binding. Every successfully resolved control results in one possible actuation.
                            numPossibleConcurrentActuations += bindingState->controlCount;
                        }
                    }

                    if (firstBindingIndexForAction == -1)
                        firstBindingIndexForAction = 0;

                    Debug.Assert(bindingCountForAction < ushort.MaxValue, "Binding count on action exceeds limit");
                    newMemory.actionBindingIndicesAndCounts[actionIndex * 2 + 1] = (ushort)bindingCountForAction;

                    // See if we may need conflict resolution on this action. Never needed for pass-through actions.
                    // Otherwise, if we have more than one bound control or have several bindings and one of them
                    // is a composite, we enable it.
                    var isPassThroughAction = action.type == InputActionType.PassThrough;
                    var isButtonAction = action.type == InputActionType.Button;
                    var mayNeedConflictResolution = !isPassThroughAction && numPossibleConcurrentActuations > 1;

                    // Initialize initial trigger state.
                    newMemory.actionStates[actionIndex] =
                        new InputActionState.TriggerState
                    {
                        phase = InputActionPhase.Disabled,
                        mapIndex = mapIndex,
                        controlIndex = InputActionState.kInvalidIndex,
                        interactionIndex = InputActionState.kInvalidIndex,
                        isPassThrough = isPassThroughAction,
                        isButton = isButtonAction,
                        mayNeedConflictResolution = mayNeedConflictResolution,
                        bindingIndex = firstBindingIndexForAction
                    };
                }

                // Store indices for map.
                newMemory.mapIndices[mapIndex] =
                    new InputActionState.ActionMapIndices
                {
                    actionStartIndex = actionStartIndex,
                    actionCount = actionCountInThisMap,
                    controlStartIndex = controlStartIndex,
                    controlCount = controlCountInThisMap,
                    bindingStartIndex = bindingStartIndex,
                    bindingCount = bindingCountInThisMap,
                    interactionStartIndex = interactionStartIndex,
                    interactionCount = totalInteractionCount - interactionStartIndex,
                    processorStartIndex = processorStartIndex,
                    processorCount = totalProcessorCount - processorStartIndex,
                    compositeStartIndex = compositeStartIndex,
                    compositeCount = totalCompositeCount - compositeStartIndex,
                };
                actionMap.m_MapIndexInState = mapIndex;
                var finalActionMapCount = memory.mapCount;
                ArrayHelpers.AppendWithCapacity(ref maps, ref finalActionMapCount, actionMap, capacityIncrement: 4);
                Debug.Assert(finalActionMapCount == newMemory.mapCount,
                    "Final action map count should match old action map count plus one");

                // As a final act, swap the new memory in.
                memory.Dispose();
                memory = newMemory;
            }
            catch (Exception)
            {
                // Don't leak our native memory when we throw an exception.
                newMemory.Dispose();
                throw;
            }
            finally
            {
                resolvedControls.Dispose();
            }
        }

        private List<NameAndParameters> m_Parameters; // We retain this to reuse the allocation.
        private int InstantiateWithParameters<TType>(TypeTable registrations, string namesAndParameters, ref TType[] array, ref int count, InputActionMap actionMap, ref InputBinding binding)
        {
            if (!NameAndParameters.ParseMultiple(namesAndParameters, ref m_Parameters))
                return InputActionState.kInvalidIndex;

            var firstIndex = count;
            for (var i = 0; i < m_Parameters.Count; ++i)
            {
                // Look up type.
                var objectRegistrationName = m_Parameters[i].name;
                var type = registrations.LookupTypeRegistration(objectRegistrationName);
                if (type == null)
                {
                    Debug.LogError(
                        $"No {typeof(TType).Name} with name '{objectRegistrationName}' (mentioned in '{namesAndParameters}') has been registered");
                    continue;
                }

                if (!m_IsControlOnlyResolve)
                {
                    // Instantiate it.
                    if (!(Activator.CreateInstance(type) is TType instance))
                    {
                        Debug.LogError(
                            $"Type '{type.Name}' registered as '{objectRegistrationName}' (mentioned in '{namesAndParameters}') is not an {typeof(TType).Name}");
                        continue;
                    }

                    // Pass parameters to it.
                    ApplyParameters(m_Parameters[i].parameters, instance, actionMap, ref binding, objectRegistrationName,
                        namesAndParameters);

                    // Add to list.
                    ArrayHelpers.AppendWithCapacity(ref array, ref count, instance);
                }
                else
                {
                    Debug.Assert(type.IsInstanceOfType(array[count]), "Type of instance in array does not match expected type");
                    ++count;
                }
            }

            return firstIndex;
        }

        private static InputBindingComposite InstantiateBindingComposite(ref InputBinding binding, InputActionMap actionMap)
        {
            var nameAndParametersParsed = NameAndParameters.Parse(binding.effectivePath);

            // Look up.
            var type = InputBindingComposite.s_Composites.LookupTypeRegistration(nameAndParametersParsed.name);
            if (type == null)
                throw new InvalidOperationException(
                    $"No binding composite with name '{nameAndParametersParsed.name}' has been registered");

            // Instantiate.
            if (!(Activator.CreateInstance(type) is InputBindingComposite instance))
                throw new InvalidOperationException(
                    $"Registered type '{type.Name}' used for '{nameAndParametersParsed.name}' is not an InputBindingComposite");

            // Set parameters.
            ApplyParameters(nameAndParametersParsed.parameters, instance, actionMap, ref binding, nameAndParametersParsed.name,
                binding.effectivePath);

            return instance;
        }

        private static void ApplyParameters(ReadOnlyArray<NamedValue> parameters, object instance, InputActionMap actionMap, ref InputBinding binding, string objectRegistrationName, string namesAndParameters)
        {
            foreach (var parameter in parameters)
            {
                // Find field.
                var field = instance.GetType().GetField(parameter.name,
                    BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
                if (field == null)
                {
                    Debug.LogError(
                        $"Type '{instance.GetType().Name}' registered as '{objectRegistrationName}' (mentioned in '{namesAndParameters}') has no public field called '{parameter.name}'");
                    continue;
                }
                var fieldTypeCode = Type.GetTypeCode(field.FieldType);

                // See if we have a parameter override.
                var parameterOverride =
                    InputActionRebindingExtensions.ParameterOverride.Find(actionMap, ref binding, parameter.name, objectRegistrationName);
                var value = parameterOverride != null
                    ? parameterOverride.Value.value
                    : parameter.value;

                field.SetValue(instance, value.ConvertTo(fieldTypeCode).ToObject());
            }
        }

        private static int AssignCompositePartIndex(object composite, string name, ref int currentCompositePartCount)
        {
            var type = composite.GetType();

            ////REVIEW: check for [InputControl] attribute?

            ////TODO: allow this to be a property instead
            // Look up field.
            var field = type.GetField(name,
                BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
            if (field == null)
                throw new InvalidOperationException(
                    $"Cannot find public field '{name}' used as parameter of binding composite '{composite}' of type '{type}'");

            ////REVIEW: should we wrap part numbers in a struct instead of using int?

            // Type-check.
            var fieldType = field.FieldType;
            if (fieldType != typeof(int))
                throw new InvalidOperationException(
                    $"Field '{name}' used as a parameter of binding composite '{composite}' must be of type 'int' but is of type '{type.Name}' instead");

            ////REVIEW: this creates garbage; need a better solution to get to zero garbage during re-resolving
            // See if we've already assigned a part index. This can happen if there are multiple bindings
            // for the same named slot on the composite (e.g. multiple "Negative" bindings on an axis composite).
            var partIndex = (int)field.GetValue(composite);
            if (partIndex == 0)
            {
                // No, not assigned yet. Create new part index.
                partIndex = ++currentCompositePartCount;
                field.SetValue(composite, partIndex);
            }

            return partIndex;
        }
    }
}