using System;
using ANX.Framework;
using ANX.Framework.Input;
using ANX.Framework.NonXNA;
using ANX.Framework.NonXNA.Development;
using SharpDX.XInput;

// This file is part of the ANX.Framework created by the
// "ANX.Framework developer group" and released under the Ms-PL license.
// For details see: http://anxframework.codeplex.com/license

namespace ANX.InputDevices.Windows.ModernUI
{
    [PercentageComplete(100)]
    [TestState(TestStateAttribute.TestState.InProgress)]
    [Developer("rene87")]
    public class GamePad : IGamePad
    {
        #region Constants
        private const int LeftThumbDeadZoneSquare = 7849 * 7849;
        private const int RightThumbDeadZoneSquare = 8689 * 8689;
        #endregion

        #region Private
        private Controller[] controller;
        private const float triggerRangeFactor = 1f / byte.MaxValue;
        private GamePadCapabilities emptyCaps = new GamePadCapabilities();
        private GamePadState emptyState = new GamePadState();
        #endregion

        #region Constructor
        public GamePad()
        {
            controller = new Controller[4];
            for (int index = 0; index < controller.Length; index++)
                controller[index] = new Controller((UserIndex)index);
        }
        #endregion

        #region GetCapabilities
        public GamePadCapabilities GetCapabilities(PlayerIndex playerIndex)
        {
            var gamepad = controller[(int)playerIndex];
            if (gamepad.IsConnected == false)
                return emptyCaps;

            try
            {
                Capabilities nativeCaps = gamepad.GetCapabilities(DeviceQueryType.Gamepad);
                return new GamePadCapabilities()
                {
                    GamePadType = FormatConverter.Translate(nativeCaps.SubType),
                    IsConnected = gamepad.IsConnected,
                    HasAButton = (nativeCaps.Gamepad.Buttons & GamepadButtonFlags.A) != 0,
                    HasBackButton = (nativeCaps.Gamepad.Buttons & GamepadButtonFlags.Back) != 0,
                    HasBButton = (nativeCaps.Gamepad.Buttons & GamepadButtonFlags.B) != 0,
                    HasDPadDownButton = (nativeCaps.Gamepad.Buttons & GamepadButtonFlags.DPadDown) != 0,
                    HasDPadLeftButton = (nativeCaps.Gamepad.Buttons & GamepadButtonFlags.DPadLeft) != 0,
                    HasDPadRightButton = (nativeCaps.Gamepad.Buttons & GamepadButtonFlags.DPadRight) != 0,
                    HasDPadUpButton = (nativeCaps.Gamepad.Buttons & GamepadButtonFlags.DPadUp) != 0,
                    HasLeftShoulderButton = (nativeCaps.Gamepad.Buttons & GamepadButtonFlags.LeftShoulder) != 0,
                    HasRightShoulderButton = (nativeCaps.Gamepad.Buttons & GamepadButtonFlags.RightShoulder) != 0,
                    HasLeftStickButton = (nativeCaps.Gamepad.Buttons & GamepadButtonFlags.LeftThumb) != 0,
                    HasRightStickButton = (nativeCaps.Gamepad.Buttons & GamepadButtonFlags.RightThumb) != 0,
                    HasStartButton = (nativeCaps.Gamepad.Buttons & GamepadButtonFlags.Start) != 0,
                    HasXButton = (nativeCaps.Gamepad.Buttons & GamepadButtonFlags.X) != 0,
                    HasYButton = (nativeCaps.Gamepad.Buttons & GamepadButtonFlags.Y) != 0,
                    HasLeftVibrationMotor = nativeCaps.Vibration.LeftMotorSpeed != 0,
                    HasRightVibrationMotor = nativeCaps.Vibration.RightMotorSpeed != 0,
                    HasVoiceSupport = (nativeCaps.Flags & CapabilityFlags.VoiceSupported) != 0,
                    HasRightXThumbStick = nativeCaps.Gamepad.RightThumbX != 0,
                    HasRightYThumbStick = nativeCaps.Gamepad.RightThumbY != 0,
                    HasLeftXThumbStick = nativeCaps.Gamepad.LeftThumbX != 0,
                    HasLeftYThumbStick = nativeCaps.Gamepad.LeftThumbY != 0,
                    HasLeftTrigger = nativeCaps.Gamepad.LeftTrigger > 0,
                    HasRightTrigger = nativeCaps.Gamepad.RightTrigger > 0,

                    // Impossible to check
                    HasBigButton = false,
                };
            }
            catch (Exception ex)
            {
                Logger.Info("Failed to get caps for gamepad " + playerIndex + ": " + ex);
                return emptyCaps;
            }
        }
        #endregion

        #region GetState
        public GamePadState GetState(PlayerIndex playerIndex)
        {
            return GetState(playerIndex, GamePadDeadZone.None);
        }

        public GamePadState GetState(PlayerIndex playerIndex, GamePadDeadZone deadZoneMode)
        {
            bool isConnected = controller[(int)playerIndex].IsConnected;
            if (isConnected == false)
                return emptyState;

            State nativeState = controller[(int)playerIndex].GetState();
            Vector2 leftThumb = ApplyDeadZone(nativeState.Gamepad.LeftThumbX, nativeState.Gamepad.LeftThumbY,
                LeftThumbDeadZoneSquare, deadZoneMode);
            Vector2 rightThumb = ApplyDeadZone(nativeState.Gamepad.RightThumbX, nativeState.Gamepad.RightThumbY,
                RightThumbDeadZoneSquare, deadZoneMode);

            return new GamePadState(leftThumb, rightThumb, nativeState.Gamepad.LeftTrigger * triggerRangeFactor,
                nativeState.Gamepad.RightTrigger * triggerRangeFactor, FormatConverter.Translate(nativeState.Gamepad.Buttons))
            {
                PacketNumber = nativeState.PacketNumber,
                IsConnected = isConnected
            };
        }
        #endregion

        #region SetVibration
        public bool SetVibration(PlayerIndex playerIndex, float leftMotor, float rightMotor)
        {
            if (controller[(int)playerIndex].IsConnected == false)
                return false;

            var vib = new Vibration()
            {
                LeftMotorSpeed = (short)(Math.Min(Math.Abs(leftMotor), 1f) * short.MaxValue),
                RightMotorSpeed = (short)(Math.Min(Math.Abs(rightMotor), 1f) * short.MaxValue),
            };
            controller[(int)playerIndex].SetVibration(vib);

            return true;
        }
        #endregion

        #region ApplyDeadZone
        private Vector2 ApplyDeadZone(int x, int y, int deadZone, GamePadDeadZone deadZoneMode)
        {
            if (deadZoneMode != GamePadDeadZone.None)
            {
                int xSquare = x * x;
                int ySquare = y * y;
                if (deadZoneMode == GamePadDeadZone.IndependentAxes)
                {
                    if (xSquare < deadZone)
                        x = 0;
                    if (ySquare < deadZone)
                        y = 0;
                }
                else if (deadZoneMode == GamePadDeadZone.Circular && xSquare + ySquare < deadZone)
                    x = y = 0;
            }

            float fx = x < 0 ? -(x / (float)short.MinValue) : x / (float)short.MaxValue;
            float fy = y < 0 ? -(y / (float)short.MinValue) : y / (float)short.MaxValue;
            return new Vector2(fx, fy);
        }
        #endregion
    }
}