using System.ComponentModel;
using UnityEngine.InputSystem.Layouts;
using UnityEngine.InputSystem.Processors;
using UnityEngine.InputSystem.Utilities;

namespace UnityEngine.InputSystem.Composites
{
    /// <summary>
    /// A single axis value computed from one axis that pulls in the <see cref="negative"/> direction (<see cref="minValue"/>) and one
    /// axis that pulls in the <see cref="positive"/> direction (<see cref="maxValue"/>).
    /// </summary>
    /// <remarks>
    /// The limits of the axis are determined by <see cref="minValue"/> and <see cref="maxValue"/>.
    /// By default, they are set to <c>[-1..1]</c>. The values can be set as parameters.
    ///
    /// <example>
    /// <code>
    /// var action = new InputAction();
    /// action.AddCompositeBinding("Axis(minValue=0,maxValue=2")
    ///     .With("Negative", "&lt;Keyboard&gt;/a")
    ///     .With("Positive", "&lt;Keyboard&gt;/d");
    /// </code>
    /// </example>
    ///
    /// If both axes are actuated at the same time, the behavior depends on <see cref="whichSideWins"/>.
    /// By default, neither side will win (<see cref="WhichSideWins.Neither"/>) and the result
    /// will be 0 (or, more precisely, the midpoint between <see cref="minValue"/> and <see cref="maxValue"/>).
    /// This can be customized to make the positive side win (<see cref="WhichSideWins.Positive"/>)
    /// or the negative one (<see cref="WhichSideWins.Negative"/>).
    ///
    /// This is useful, for example, in a driving game where break should cancel out accelerate.
    /// By binding <see cref="negative"/> to the break control(s) and <see cref="positive"/> to the
    /// acceleration control(s), and setting <see cref="whichSideWins"/> to <see cref="WhichSideWins.Negative"/>,
    /// if the break button is pressed, it will always cause the acceleration button to be ignored.
    ///
    /// The actual <em>absolute</em> values of <see cref="negative"/> and <see cref="positive"/> are used
    /// to scale <see cref="minValue"/> and <see cref="maxValue"/> respectively. So if, for example, <see cref="positive"/>
    /// is bound to <see cref="Gamepad.rightTrigger"/> and the trigger is at a value of 0.5, then the resulting
    /// value is <c>maxValue * 0.5</c> (the actual formula is <c>midPoint + (maxValue - midPoint) * positive</c>).
    /// </remarks>
    [DisplayStringFormat("{negative}/{positive}")]
    [DisplayName("Positive/Negative Binding")]
    public class AxisComposite : InputBindingComposite<float>
    {
        /// <summary>
        /// Binding for the axis input that controls the negative [<see cref="minValue"/>..0] direction of the
        /// combined axis.
        /// </summary>
        /// <remarks>
        /// This property is automatically assigned by the input system.
        /// </remarks>
        // ReSharper disable once MemberCanBePrivate.Global
        // ReSharper disable once FieldCanBeMadeReadOnly.Global
        [InputControl(layout = "Axis")] public int negative = 0;

        /// <summary>
        /// Binding for the axis input that controls the positive [0..<see cref="maxValue"/>] direction of the
        /// combined axis.
        /// </summary>
        /// <remarks>
        /// This property is automatically assigned by the input system.
        /// </remarks>
        // ReSharper disable once MemberCanBePrivate.Global
        // ReSharper disable once FieldCanBeMadeReadOnly.Global
        [InputControl(layout = "Axis")] public int positive = 0;

        /// <summary>
        /// The lower bound that the axis is limited to. -1 by default.
        /// </summary>
        /// <remarks>
        /// This value corresponds to the full actuation of the control(s) bound to <see cref="negative"/>.
        ///
        /// <example>
        /// <code>
        /// var action = new InputAction();
        /// action.AddCompositeBinding("Axis(minValue=0,maxValue=2")
        ///     .With("Negative", "&lt;Keyboard&gt;/a")
        ///     .With("Positive", "&lt;Keyboard&gt;/d");
        /// </code>
        /// </example>
        /// </remarks>
        /// <seealso cref="maxValue"/>
        /// <seealso cref="negative"/>
        // ReSharper disable once MemberCanBePrivate.Global
        // ReSharper disable once FieldCanBeMadeReadOnly.Global
        [Tooltip("Value to return when the negative side is fully actuated.")]
        public float minValue = -1;

        /// <summary>
        /// The upper bound that the axis is limited to. 1 by default.
        /// </summary>
        /// <remarks>
        /// This value corresponds to the full actuation of the control(s) bound to <see cref="positive"/>.
        ///
        /// <example>
        /// <code>
        /// var action = new InputAction();
        /// action.AddCompositeBinding("Axis(minValue=0,maxValue=2")
        ///     .With("Negative", "&lt;Keyboard&gt;/a")
        ///     .With("Positive", "&lt;Keyboard&gt;/d");
        /// </code>
        /// </example>
        /// </remarks>
        /// <seealso cref="minValue"/>
        /// <seealso cref="positive"/>
        // ReSharper disable once MemberCanBePrivate.Global
        // ReSharper disable once FieldCanBeMadeReadOnly.Global
        [Tooltip("Value to return when the positive side is fully actuated.")]
        public float maxValue = 1;

        /// <summary>
        /// If both the <see cref="positive"/> and <see cref="negative"/> button are actuated, this
        /// determines which value is returned from the composite.
        /// </summary>
        [Tooltip("If both the positive and negative side are actuated, decides what value to return. 'Neither' (default) means that " +
            "the resulting value is the midpoint between min and max. 'Positive' means that max will be returned. 'Negative' means that " +
            "min will be returned.")]
        public WhichSideWins whichSideWins = WhichSideWins.Neither;

        /// <summary>
        /// The value that is returned if the composite is in a neutral position, that is, if
        /// neither <see cref="positive"/> nor <see cref="negative"/> are actuated or if
        /// <see cref="whichSideWins"/> is set to <see cref="WhichSideWins.Neither"/> and
        /// both <see cref="positive"/> and <see cref="negative"/> are actuated.
        /// </summary>
        public float midPoint => (maxValue + minValue) / 2;

        ////TODO: add parameters to control ramp up&down

        /// <inheritdoc />
        public override float ReadValue(ref InputBindingCompositeContext context)
        {
            var negativeValue = Mathf.Abs(context.ReadValue<float>(negative));
            var positiveValue = Mathf.Abs(context.ReadValue<float>(positive));

            var negativeIsActuated = negativeValue > Mathf.Epsilon;
            var positiveIsActuated = positiveValue > Mathf.Epsilon;

            if (negativeIsActuated == positiveIsActuated)
            {
                switch (whichSideWins)
                {
                    case WhichSideWins.Negative:
                        positiveIsActuated = false;
                        break;

                    case WhichSideWins.Positive:
                        negativeIsActuated = false;
                        break;

                    case WhichSideWins.Neither:
                        return midPoint;
                }
            }

            var mid = midPoint;

            if (negativeIsActuated)
                return mid - (mid - minValue) * negativeValue;

            return mid + (maxValue - mid) * positiveValue;
        }

        /// <inheritdoc />
        public override float EvaluateMagnitude(ref InputBindingCompositeContext context)
        {
            var value = ReadValue(ref context);
            if (value < midPoint)
            {
                value = Mathf.Abs(value - midPoint);
                return NormalizeProcessor.Normalize(value, 0, Mathf.Abs(minValue), 0);
            }

            value = Mathf.Abs(value - midPoint);
            return NormalizeProcessor.Normalize(value, 0, Mathf.Abs(maxValue), 0);
        }

        /// <summary>
        /// What happens to the value of an <see cref="AxisComposite"/> if both <see cref="positive"/>
        /// and <see cref="negative"/> are actuated at the same time.
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1717:OnlyFlagsEnumsShouldHavePluralNames", Justification = "False positive: `Wins` is not a plural form.")]
        public enum WhichSideWins
        {
            /// <summary>
            /// If both <see cref="positive"/> and <see cref="negative"/> are actuated, the sides cancel
            /// each other out and the result is 0.
            /// </summary>
            Neither = 0,

            /// <summary>
            /// If both <see cref="positive"/> and <see cref="negative"/> are actuated, the value of
            /// <see cref="positive"/> wins and <see cref="negative"/> is ignored.
            /// </summary>
            Positive = 1,

            /// <summary>
            /// If both <see cref="positive"/> and <see cref="negative"/> are actuated, the value of
            /// <see cref="negative"/> wins and <see cref="positive"/> is ignored.
            /// </summary>
            Negative = 2,
        }
    }
}