using System;
using System.Collections.Generic;

namespace UnityEngine.InputSystem
{
    /// <summary>
    /// Contextual data made available when processing values of composite bindings.
    /// </summary>
    /// <remarks>
    /// An instance of this struct is passed to <see
    /// cref="InputBindingComposite{TValue}.ReadValue(ref InputBindingCompositeContext)"/>.
    /// Use it to access contextual data such as the value for individual part bindings.
    ///
    /// Note that an instance of this struct should never be held on to past the duration
    /// of the call to <c>ReadValue</c>. The data it retrieves is only valid during
    /// the callback.
    /// </remarks>
    /// <seealso cref="InputBindingComposite"/>
    /// <seealso cref="InputBindingComposite{TValue}"/>
    /// <seealso cref="InputBindingComposite{TValue}.ReadValue(ref InputBindingCompositeContext)"/>
    public struct InputBindingCompositeContext
    {
        /// <summary>
        /// Information about a control bound to a part of a composite.
        /// </summary>
        public struct PartBinding
        {
            /// <summary>
            /// Identifier of the part. This is the numeric identifier stored in the public
            /// fields of the composite by the input system.
            /// </summary>
            public int part { get; set; }

            /// <summary>
            /// The control bound to the part.
            /// </summary>
            public InputControl control { get; set; }
        }

        /// <summary>
        /// Enumerate all the controls that are part of the composite.
        /// </summary>
        /// <seealso cref="InputBindingComposite.FinishSetup"/>
        public IEnumerable<PartBinding> controls
        {
            get
            {
                if (m_State == null)
                    yield break;

                var totalBindingCount = m_State.totalBindingCount;
                for (var bindingIndex = m_BindingIndex + 1; bindingIndex < totalBindingCount; ++bindingIndex)
                {
                    var bindingState = m_State.GetBindingState(bindingIndex);
                    if (!bindingState.isPartOfComposite)
                        break;

                    var controlStartIndex = bindingState.controlStartIndex;
                    for (var i = 0; i < bindingState.controlCount; ++i)
                    {
                        var control = m_State.controls[controlStartIndex + i];
                        yield return new PartBinding
                        {
                            part = bindingState.partIndex,
                            control = control
                        };
                    }
                }
            }
        }

        public float EvaluateMagnitude(int partNumber)
        {
            return m_State.EvaluateCompositePartMagnitude(m_BindingIndex, partNumber);
        }

        /// <summary>
        /// Read the value of the giving part binding.
        /// </summary>
        /// <param name="partNumber">Number of the part to read. This is assigned
        /// automatically by the input system and should be treated as an opaque
        /// identifier. See the example below.</param>
        /// <typeparam name="TValue">Type of value to read. This must match the
        /// value type expected from controls bound to the part.</typeparam>
        /// <returns>The value read from the part bindings.</returns>
        /// <exception cref="InvalidOperationException">The given <typeparamref name="TValue"/>
        /// value type does not match the actual value type of the control(s) bound
        /// to the part.</exception>
        /// <remarks>
        /// If no control is bound to the given part, the return value will always
        /// be <c>default(TValue)</c>. If a single control is bound to the part, the
        /// value will be that of the control. If multiple controls are bound to a
        /// part, the return value will be that greatest one according to <c>IComparable</c>
        /// implemented by <typeparamref name="TValue"/>.
        ///
        /// Note that this method only works with values that are <c>IComparable</c>.
        /// To read a value type that is not <c>IComparable</c> or to supply a custom
        /// comparer, use <see cref="ReadValue{TValue,TComparer}(int,TComparer)"/>.
        ///
        /// If an invalid <paramref name="partNumber"/> is supplied, the return value
        /// will simply be <c>default(TValue)</c>. No exception is thrown.
        ///
        /// <example>
        /// <code>
        /// public class MyComposite : InputBindingComposite&lt;float&gt;
        /// {
        ///     // Defines a "part" binding for the composite. Each part can be
        ///     // bound to arbitrary many times (including not at all). The "layout"
        ///     // property of the attribute we supply determines what kind of
        ///     // control is expected to be bound to the part.
        ///     //
        ///     // When initializing a composite instance, the input system will
        ///     // automatically assign part numbers and store them in the fields
        ///     // we define here.
        ///     [InputControl(layout = "Button")]
        ///     public int firstPart;
        ///
        ///     // Defines a second part.
        ///     [InputControl(layout = "Vector2")]
        ///     public int secondPart;
        ///
        ///     public override float ReadValue(ref InputBindingCompositeContext context)
        ///     {
        ///         // Read the button.
        ///         var firstValue = context.ReadValue&lt;float&gt;();
        ///
        ///         // Read the vector.
        ///         var secondValue = context.ReadValue&lt;Vector2&gt;();
        ///
        ///         // Perform some computation based on the inputs. Here, we just
        ///         // scale the vector by the value we got from the button.
        ///         return secondValue * firstValue;
        ///     }
        /// }
        /// </code>
        /// </example>
        /// </remarks>
        /// <seealso cref="ReadValue{TValue,TComparer}(int,TComparer)"/>
        /// <seealso cref="InputControl{TValue}.ReadValue"/>
        public unsafe TValue ReadValue<TValue>(int partNumber)
            where TValue : struct, IComparable<TValue>
        {
            if (m_State == null)
                return default;

            return m_State.ReadCompositePartValue<TValue, DefaultComparer<TValue>>
                    (m_BindingIndex, partNumber, null, out _);
        }

        /// <summary>
        /// Same as <see cref="ReadValue{TValue}(int)"/> but also return the control
        /// from which the value was read.
        /// </summary>
        /// <param name="partNumber">Number of the part to read. This is assigned
        /// automatically by the input system and should be treated as an opaque
        /// identifier.</param>
        /// <param name="sourceControl">Receives the <see cref="InputControl"/> from
        /// which the value was read. If multiple controls are bound to the given part,
        /// this is the control whose value was ultimately selected. Will be set to
        /// <c>null</c> if <paramref name="partNumber"/> is not a valid part or if no
        /// controls are bound to the part.</param>
        /// <typeparam name="TValue">Type of value to read. This must match the
        /// value type expected from controls bound to the part.</typeparam>
        /// <returns>The value read from the part bindings.</returns>
        /// <remarks>
        /// Like <see cref="ReadValue{TValue}(int)"/>, this method relies on using <c>IComparable</c>
        /// implemented by <typeparamref name="TValue"/> to determine the greatest value
        /// if multiple controls are bound to the specified part.
        /// </remarks>
        /// <seealso cref="ReadValue{TValue}(int)"/>
        public unsafe TValue ReadValue<TValue>(int partNumber, out InputControl sourceControl)
            where TValue : struct, IComparable<TValue>
        {
            if (m_State == null)
            {
                sourceControl = null;
                return default;
            }

            var value = m_State.ReadCompositePartValue<TValue, DefaultComparer<TValue>>(m_BindingIndex, partNumber,
                null, out var controlIndex);
            if (controlIndex != InputActionState.kInvalidIndex)
                sourceControl = m_State.controls[controlIndex];
            else
                sourceControl = null;

            return value;
        }

        ////TODO: once we can break the API, remove the versions that rely on comparers and do everything through magnitude

        /// <summary>
        /// Read the value of the given part bindings and use the given <paramref name="comparer"/>
        /// to determine which value to return if multiple controls are bound to the part.
        /// </summary>
        /// <param name="partNumber">Number of the part to read. This is assigned
        /// automatically by the input system and should be treated as an opaque
        /// identifier.</param>
        /// <param name="comparer">Instance of <typeparamref name="TComparer"/> for comparing
        /// multiple values.</param>
        /// <typeparam name="TValue">Type of value to read. This must match the
        /// value type expected from controls bound to the part.</typeparam>
        /// <returns>The value read from the part bindings.</returns>
        /// <typeparam name="TComparer">Comparer to use if multiple controls are bound to
        /// the given part. All values will be compared using <c>TComparer.Compare</c> and
        /// the greatest value will be returned.</typeparam>
        /// <returns>The value read from the part bindings.</returns>
        /// <remarks>
        /// This method is a useful alternative to <see cref="ReadValue{TValue}(int)"/> for
        /// value types that do not implement <c>IComparable</c> or when the default comparison
        /// behavior is undesirable.
        ///
        /// <example>
        /// <code>
        /// public class CompositeWithVector2Part : InputBindingComposite&lt;Vector2&gt;
        /// {
        ///     [InputControl(layout = "Vector2")]
        ///     public int part;
        ///
        ///     public override Vector2 ReadValue(ref InputBindingCompositeContext context)
        ///     {
        ///         // Return the Vector3 with the greatest magnitude.
        ///         return context.ReadValue&lt;Vector2, Vector2MagnitudeComparer&gt;(part);
        ///     }
        /// }
        /// </code>
        /// </example>
        /// </remarks>
        /// <seealso cref="Utilities.Vector2MagnitudeComparer"/>
        /// <seealso cref="Utilities.Vector3MagnitudeComparer"/>
        public unsafe TValue ReadValue<TValue, TComparer>(int partNumber, TComparer comparer = default)
            where TValue : struct
            where TComparer : IComparer<TValue>
        {
            if (m_State == null)
                return default;

            return m_State.ReadCompositePartValue<TValue, TComparer>(
                m_BindingIndex, partNumber, null, out _, comparer);
        }

        /// <summary>
        /// Like <see cref="ReadValue{TValue,TComparer}(int,TComparer)"/> but also return
        /// the control from which the value has ultimately been read.
        /// </summary>
        /// <param name="partNumber">Number of the part to read. This is assigned
        /// automatically by the input system and should be treated as an opaque
        /// identifier.</param>
        /// <param name="sourceControl">Receives the <see cref="InputControl"/> from
        /// which the value was read. If multiple controls are bound to the given part,
        /// this is the control whose value was ultimately selected. Will be set to
        /// <c>null</c> if <paramref name="partNumber"/> is not a valid part or if no
        /// controls are bound to the part.</param>
        /// <param name="comparer">Instance of <typeparamref name="TComparer"/> for comparing
        /// multiple values.</param>
        /// <typeparam name="TValue">Type of value to read. This must match the
        /// value type expected from controls bound to the part.</typeparam>
        /// <returns>The value read from the part bindings.</returns>
        /// <typeparam name="TComparer">Comparer to use if multiple controls are bound to
        /// the given part. All values will be compared using <c>TComparer.Compare</c> and
        /// the greatest value will be returned.</typeparam>
        /// <returns>The value read from the part bindings.</returns>
        public unsafe TValue ReadValue<TValue, TComparer>(int partNumber, out InputControl sourceControl, TComparer comparer = default)
            where TValue : struct
            where TComparer : IComparer<TValue>
        {
            if (m_State == null)
            {
                sourceControl = null;
                return default;
            }

            var value = m_State.ReadCompositePartValue<TValue, TComparer>(m_BindingIndex, partNumber, null,
                out var controlIndex, comparer);

            if (controlIndex != InputActionState.kInvalidIndex)
                sourceControl = m_State.controls[controlIndex];
            else
                sourceControl = null;

            return value;
        }

        /// <summary>
        /// Like <see cref="ReadValue{TValue}(int)"/> but treat bound controls as buttons. This means
        /// that custom <see cref="Controls.ButtonControl.pressPoint"/> are respected and that floating-point
        /// values from non-ButtonControls will be compared to <see cref="InputSettings.defaultButtonPressPoint"/>.
        /// </summary>
        /// <param name="partNumber">Number of the part to read. This is assigned
        /// automatically by the input system and should be treated as an opaque
        /// identifier.</param>
        /// <returns>True if any button bound to the part is pressed.</returns>
        /// <remarks>
        /// This method expects all controls bound to the part to be of type <c>InputControl&lt;float&gt;</c>.
        ///
        /// This method is different from just calling <see cref="ReadValue{TValue}(int)"/> with a <c>float</c>
        /// parameter and comparing the result to <see cref="InputSettings.defaultButtonPressPoint"/> in that
        /// custom press points set on individual ButtonControls will be respected.
        /// </remarks>
        /// <seealso cref="Controls.ButtonControl"/>
        /// <seealso cref="InputSettings.defaultButtonPressPoint"/>
        public unsafe bool ReadValueAsButton(int partNumber)
        {
            if (m_State == null)
                return default;

            ////REVIEW: wouldn't this have to take release points into account now?

            var buttonValue = false;
            m_State.ReadCompositePartValue<float, DefaultComparer<float>>(m_BindingIndex, partNumber, &buttonValue,
                out _);
            return buttonValue;
        }

        public unsafe void ReadValue(int partNumber, void* buffer, int bufferSize)
        {
            m_State?.ReadCompositePartValue(m_BindingIndex, partNumber, buffer, bufferSize);
        }

        public object ReadValueAsObject(int partNumber)
        {
            return m_State.ReadCompositePartValueAsObject(m_BindingIndex, partNumber);
        }

        /// <summary>
        /// Return the timestamp (see <see cref="LowLevel.InputEvent.time"/>) for when the given
        /// binding part crossed the button press threshold (see <see cref="Controls.ButtonControl.pressPoint"/>).
        /// </summary>
        /// <param name="partNumber">Number of the part to read. This is assigned
        /// automatically by the input system and should be treated as an opaque
        /// identifier.</param>
        /// <returns>Returns the time at which the given part binding moved into "press" state or 0 if there's
        /// current no press.</returns>
        /// <remarks>
        /// If the given part has more than a single binding and/or more than a single bound control, the <em>earliest</em>
        /// press time is returned.
        /// </remarks>
        public double GetPressTime(int partNumber)
        {
            return m_State.GetCompositePartPressTime(m_BindingIndex, partNumber);
        }

        internal InputActionState m_State;
        internal int m_BindingIndex;

        private struct DefaultComparer<TValue> : IComparer<TValue>
            where TValue : IComparable<TValue>
        {
            public int Compare(TValue x, TValue y)
            {
                return x.CompareTo(y);
            }
        }
    }
}