using Gtk;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Common.Configuration.Hid.Controller.Motion;
using Ryujinx.Common.Configuration.Hid.Keyboard;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.Input;
using Ryujinx.Input.Assigner;
using Ryujinx.Input.GTK3;
using Ryujinx.UI.Common.Configuration;
using Ryujinx.UI.Helper;
using Ryujinx.UI.Widgets;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text.Json;
using System.Threading;
using Button = Ryujinx.Input.Button;
using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId;
using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId;
using GUI = Gtk.Builder.ObjectAttribute;
using Key = Ryujinx.Common.Configuration.Hid.Key;

namespace Ryujinx.UI.Windows
{
    public class ControllerWindow : Window
    {
        private readonly PlayerIndex _playerIndex;
        private readonly InputConfig _inputConfig;

        private bool _isWaitingForInput;

#pragma warning disable CS0649, IDE0044 // Field is never assigned to, Add readonly modifier
        [GUI] Adjustment _controllerStrongRumble;
        [GUI] Adjustment _controllerWeakRumble;
        [GUI] Adjustment _controllerDeadzoneLeft;
        [GUI] Adjustment _controllerDeadzoneRight;
        [GUI] Adjustment _controllerRangeLeft;
        [GUI] Adjustment _controllerRangeRight;
        [GUI] Adjustment _controllerTriggerThreshold;
        [GUI] Adjustment _slotNumber;
        [GUI] Adjustment _altSlotNumber;
        [GUI] Adjustment _sensitivity;
        [GUI] Adjustment _gyroDeadzone;
        [GUI] CheckButton _enableMotion;
        [GUI] CheckButton _enableCemuHook;
        [GUI] CheckButton _mirrorInput;
        [GUI] Entry _dsuServerHost;
        [GUI] Entry _dsuServerPort;
        [GUI] ComboBoxText _inputDevice;
        [GUI] ComboBoxText _profile;
        [GUI] Box _settingsBox;
        [GUI] Box _motionAltBox;
        [GUI] Box _motionBox;
        [GUI] Box _dsuServerHostBox;
        [GUI] Box _dsuServerPortBox;
        [GUI] Box _motionControllerSlot;
        [GUI] Grid _leftStickKeyboard;
        [GUI] Grid _leftStickController;
        [GUI] Box _deadZoneLeftBox;
        [GUI] Box _rangeLeftBox;
        [GUI] Grid _rightStickKeyboard;
        [GUI] Grid _rightStickController;
        [GUI] Box _deadZoneRightBox;
        [GUI] Box _rangeRightBox;
        [GUI] Grid _leftSideTriggerBox;
        [GUI] Grid _rightSideTriggerBox;
        [GUI] Box _triggerThresholdBox;
        [GUI] ComboBoxText _controllerType;
        [GUI] ToggleButton _lStick;
        [GUI] CheckButton _invertLStickX;
        [GUI] CheckButton _invertLStickY;
        [GUI] CheckButton _rotateL90CW;
        [GUI] ToggleButton _lStickUp;
        [GUI] ToggleButton _lStickDown;
        [GUI] ToggleButton _lStickLeft;
        [GUI] ToggleButton _lStickRight;
        [GUI] ToggleButton _lStickButton;
        [GUI] ToggleButton _dpadUp;
        [GUI] ToggleButton _dpadDown;
        [GUI] ToggleButton _dpadLeft;
        [GUI] ToggleButton _dpadRight;
        [GUI] ToggleButton _minus;
        [GUI] ToggleButton _l;
        [GUI] ToggleButton _zL;
        [GUI] ToggleButton _rStick;
        [GUI] CheckButton _invertRStickX;
        [GUI] CheckButton _invertRStickY;
        [GUI] CheckButton _rotateR90CW;
        [GUI] ToggleButton _rStickUp;
        [GUI] ToggleButton _rStickDown;
        [GUI] ToggleButton _rStickLeft;
        [GUI] ToggleButton _rStickRight;
        [GUI] ToggleButton _rStickButton;
        [GUI] ToggleButton _a;
        [GUI] ToggleButton _b;
        [GUI] ToggleButton _x;
        [GUI] ToggleButton _y;
        [GUI] ToggleButton _plus;
        [GUI] ToggleButton _r;
        [GUI] ToggleButton _zR;
        [GUI] ToggleButton _lSl;
        [GUI] ToggleButton _lSr;
        [GUI] ToggleButton _rSl;
        [GUI] ToggleButton _rSr;
        [GUI] Image _controllerImage;
        [GUI] CheckButton _enableRumble;
        [GUI] Box _rumbleBox;
#pragma warning restore CS0649, IDE0044

        private readonly MainWindow _mainWindow;
        private readonly IGamepadDriver _gtk3KeyboardDriver;
        private IGamepad _selectedGamepad;
        private bool _mousePressed;
        private bool _middleMousePressed;

        private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());

        public ControllerWindow(MainWindow mainWindow, PlayerIndex controllerId) : this(mainWindow, new Builder("Ryujinx.Gtk3.UI.Windows.ControllerWindow.glade"), controllerId) { }

        private ControllerWindow(MainWindow mainWindow, Builder builder, PlayerIndex controllerId) : base(builder.GetRawOwnedObject("_controllerWin"))
        {
            _mainWindow = mainWindow;
            _selectedGamepad = null;

            // NOTE: To get input in this window, we need to bind a custom keyboard driver instead of using the InputManager one as the main window isn't focused...
            _gtk3KeyboardDriver = new GTK3KeyboardDriver(this);

            Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png");

            builder.Autoconnect(this);

            _playerIndex = controllerId;
            _inputConfig = ConfigurationState.Instance.Hid.InputConfig.Value.Find(inputConfig => inputConfig.PlayerIndex == _playerIndex);

            Title = $"Ryujinx - Controller Settings - {_playerIndex}";

            if (_playerIndex == PlayerIndex.Handheld)
            {
                _controllerType.Append(ControllerType.Handheld.ToString(), "Handheld");
                _controllerType.Sensitive = false;
            }
            else
            {
                _controllerType.Append(ControllerType.ProController.ToString(), "Pro Controller");
                _controllerType.Append(ControllerType.JoyconPair.ToString(), "Joycon Pair");
                _controllerType.Append(ControllerType.JoyconLeft.ToString(), "Joycon Left");
                _controllerType.Append(ControllerType.JoyconRight.ToString(), "Joycon Right");
            }

            _controllerType.Active = 0; // Set initial value to first in list.

            // Bind Events.
            _lStick.Clicked += ButtonForStick_Pressed;
            _lStickUp.Clicked += Button_Pressed;
            _lStickDown.Clicked += Button_Pressed;
            _lStickLeft.Clicked += Button_Pressed;
            _lStickRight.Clicked += Button_Pressed;
            _lStickButton.Clicked += Button_Pressed;
            _dpadUp.Clicked += Button_Pressed;
            _dpadDown.Clicked += Button_Pressed;
            _dpadLeft.Clicked += Button_Pressed;
            _dpadRight.Clicked += Button_Pressed;
            _minus.Clicked += Button_Pressed;
            _l.Clicked += Button_Pressed;
            _zL.Clicked += Button_Pressed;
            _lSl.Clicked += Button_Pressed;
            _lSr.Clicked += Button_Pressed;
            _rStick.Clicked += ButtonForStick_Pressed;
            _rStickUp.Clicked += Button_Pressed;
            _rStickDown.Clicked += Button_Pressed;
            _rStickLeft.Clicked += Button_Pressed;
            _rStickRight.Clicked += Button_Pressed;
            _rStickButton.Clicked += Button_Pressed;
            _a.Clicked += Button_Pressed;
            _b.Clicked += Button_Pressed;
            _x.Clicked += Button_Pressed;
            _y.Clicked += Button_Pressed;
            _plus.Clicked += Button_Pressed;
            _r.Clicked += Button_Pressed;
            _zR.Clicked += Button_Pressed;
            _rSl.Clicked += Button_Pressed;
            _rSr.Clicked += Button_Pressed;
            _enableCemuHook.Clicked += CemuHookCheckButtonPressed;

            // Setup current values.
            UpdateInputDeviceList();
            SetAvailableOptions();

            ClearValues();
            if (_inputDevice.ActiveId != null)
            {
                SetCurrentValues();
            }

            mainWindow.InputManager.GamepadDriver.OnGamepadConnected += HandleOnGamepadConnected;
            mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected;

            _mainWindow.RendererWidget?.NpadManager.BlockInputUpdates();
        }

        private void CemuHookCheckButtonPressed(object sender, EventArgs e)
        {
            UpdateCemuHookSpecificFieldsVisibility();
        }

        private void HandleOnGamepadDisconnected(string id)
        {
            Application.Invoke(delegate
            {
                UpdateInputDeviceList();
            });
        }

        private void HandleOnGamepadConnected(string id)
        {
            Application.Invoke(delegate
            {
                UpdateInputDeviceList();
            });
        }

        protected override void OnDestroyed()
        {
            _mainWindow.InputManager.GamepadDriver.OnGamepadConnected -= HandleOnGamepadConnected;
            _mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected -= HandleOnGamepadDisconnected;

            _mainWindow.RendererWidget?.NpadManager.UnblockInputUpdates();

            _selectedGamepad?.Dispose();

            _gtk3KeyboardDriver.Dispose();
        }

        private static string GetShortGamepadName(string str)
        {
            const string ShrinkChars = "...";
            const int MaxSize = 50;

            if (str.Length > MaxSize)
            {
                return $"{str.AsSpan(0, MaxSize - ShrinkChars.Length)}{ShrinkChars}";
            }

            return str;
        }

        private void UpdateInputDeviceList()
        {
            _inputDevice.RemoveAll();
            _inputDevice.Append("disabled", "Disabled");
            _inputDevice.SetActiveId("disabled");

            foreach (string id in _mainWindow.InputManager.KeyboardDriver.GamepadsIds)
            {
                IGamepad gamepad = _mainWindow.InputManager.KeyboardDriver.GetGamepad(id);

                if (gamepad != null)
                {
                    _inputDevice.Append($"keyboard/{id}", GetShortGamepadName($"{gamepad.Name} ({id})"));

                    gamepad.Dispose();
                }
            }

            foreach (string id in _mainWindow.InputManager.GamepadDriver.GamepadsIds)
            {
                IGamepad gamepad = _mainWindow.InputManager.GamepadDriver.GetGamepad(id);

                if (gamepad != null)
                {
                    _inputDevice.Append($"controller/{id}", GetShortGamepadName($"{gamepad.Name} ({id})"));

                    gamepad.Dispose();
                }
            }

            switch (_inputConfig)
            {
                case StandardKeyboardInputConfig keyboard:
                    _inputDevice.SetActiveId($"keyboard/{keyboard.Id}");
                    break;
                case StandardControllerInputConfig controller:
                    _inputDevice.SetActiveId($"controller/{controller.Id}");
                    break;
            }
        }

        private void UpdateCemuHookSpecificFieldsVisibility()
        {
            if (_enableCemuHook.Active)
            {
                _dsuServerHostBox.Show();
                _dsuServerPortBox.Show();
                _motionControllerSlot.Show();
                _motionAltBox.Show();
                _mirrorInput.Show();
            }
            else
            {
                _dsuServerHostBox.Hide();
                _dsuServerPortBox.Hide();
                _motionControllerSlot.Hide();
                _motionAltBox.Hide();
                _mirrorInput.Hide();
            }
        }

        private void SetAvailableOptions()
        {
            if (_inputDevice.ActiveId != null && _inputDevice.ActiveId.StartsWith("keyboard"))
            {
                ShowAll();
                _leftStickController.Hide();
                _rightStickController.Hide();
                _deadZoneLeftBox.Hide();
                _deadZoneRightBox.Hide();
                _rangeLeftBox.Hide();
                _rangeRightBox.Hide();
                _triggerThresholdBox.Hide();
                _motionBox.Hide();
                _rumbleBox.Hide();
            }
            else if (_inputDevice.ActiveId != null && _inputDevice.ActiveId.StartsWith("controller"))
            {
                ShowAll();
                _leftStickKeyboard.Hide();
                _rightStickKeyboard.Hide();

                UpdateCemuHookSpecificFieldsVisibility();
            }
            else
            {
                _settingsBox.Hide();
            }

            ClearValues();
        }

        private void SetCurrentValues()
        {
            SetControllerSpecificFields();

            SetProfiles();

            if (_inputDevice.ActiveId.StartsWith("keyboard") && _inputConfig is StandardKeyboardInputConfig)
            {
                SetValues(_inputConfig);
            }
            else if (_inputDevice.ActiveId.StartsWith("controller") && _inputConfig is StandardControllerInputConfig)
            {
                SetValues(_inputConfig);
            }
        }

        private void SetControllerSpecificFields()
        {
            _leftSideTriggerBox.Hide();
            _rightSideTriggerBox.Hide();
            _motionAltBox.Hide();

            switch (_controllerType.ActiveId)
            {
                case "JoyconLeft":
                    _leftSideTriggerBox.Show();
                    break;
                case "JoyconRight":
                    _rightSideTriggerBox.Show();
                    break;
                case "JoyconPair":
                    _motionAltBox.Show();
                    break;
            }

            if (!OperatingSystem.IsMacOS())
            {
                _controllerImage.Pixbuf = _controllerType.ActiveId switch
                {
                    "ProController" => new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Controller_ProCon.svg", 400, 400),
                    "JoyconLeft" => new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Controller_JoyConLeft.svg", 400, 500),
                    "JoyconRight" => new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Controller_JoyConRight.svg", 400, 500),
                    _ => new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Controller_JoyConPair.svg", 400, 500),
                };
            }
        }

        private void ClearValues()
        {
            _lStick.Label = "Unbound";
            _lStickUp.Label = "Unbound";
            _lStickDown.Label = "Unbound";
            _lStickLeft.Label = "Unbound";
            _lStickRight.Label = "Unbound";
            _lStickButton.Label = "Unbound";
            _dpadUp.Label = "Unbound";
            _dpadDown.Label = "Unbound";
            _dpadLeft.Label = "Unbound";
            _dpadRight.Label = "Unbound";
            _minus.Label = "Unbound";
            _l.Label = "Unbound";
            _zL.Label = "Unbound";
            _lSl.Label = "Unbound";
            _lSr.Label = "Unbound";
            _rStick.Label = "Unbound";
            _rStickUp.Label = "Unbound";
            _rStickDown.Label = "Unbound";
            _rStickLeft.Label = "Unbound";
            _rStickRight.Label = "Unbound";
            _rStickButton.Label = "Unbound";
            _a.Label = "Unbound";
            _b.Label = "Unbound";
            _x.Label = "Unbound";
            _y.Label = "Unbound";
            _plus.Label = "Unbound";
            _r.Label = "Unbound";
            _zR.Label = "Unbound";
            _rSl.Label = "Unbound";
            _rSr.Label = "Unbound";
            _controllerStrongRumble.Value = 1;
            _controllerWeakRumble.Value = 1;
            _controllerDeadzoneLeft.Value = 0;
            _controllerDeadzoneRight.Value = 0;
            _controllerRangeLeft.Value = 1;
            _controllerRangeRight.Value = 1;
            _controllerTriggerThreshold.Value = 0;
            _mirrorInput.Active = false;
            _enableMotion.Active = false;
            _enableCemuHook.Active = false;
            _slotNumber.Value = 0;
            _altSlotNumber.Value = 0;
            _sensitivity.Value = 100;
            _gyroDeadzone.Value = 1;
            _dsuServerHost.Buffer.Text = "";
            _dsuServerPort.Buffer.Text = "";
            _enableRumble.Active = false;
        }

        private void SetValues(InputConfig config)
        {
            switch (config)
            {
                case StandardKeyboardInputConfig keyboardConfig:
                    if (!_controllerType.SetActiveId(keyboardConfig.ControllerType.ToString()))
                    {
                        _controllerType.SetActiveId(_playerIndex == PlayerIndex.Handheld
                            ? ControllerType.Handheld.ToString()
                            : ControllerType.ProController.ToString());
                    }

                    _lStickUp.Label = keyboardConfig.LeftJoyconStick.StickUp.ToString();
                    _lStickDown.Label = keyboardConfig.LeftJoyconStick.StickDown.ToString();
                    _lStickLeft.Label = keyboardConfig.LeftJoyconStick.StickLeft.ToString();
                    _lStickRight.Label = keyboardConfig.LeftJoyconStick.StickRight.ToString();
                    _lStickButton.Label = keyboardConfig.LeftJoyconStick.StickButton.ToString();
                    _dpadUp.Label = keyboardConfig.LeftJoycon.DpadUp.ToString();
                    _dpadDown.Label = keyboardConfig.LeftJoycon.DpadDown.ToString();
                    _dpadLeft.Label = keyboardConfig.LeftJoycon.DpadLeft.ToString();
                    _dpadRight.Label = keyboardConfig.LeftJoycon.DpadRight.ToString();
                    _minus.Label = keyboardConfig.LeftJoycon.ButtonMinus.ToString();
                    _l.Label = keyboardConfig.LeftJoycon.ButtonL.ToString();
                    _zL.Label = keyboardConfig.LeftJoycon.ButtonZl.ToString();
                    _lSl.Label = keyboardConfig.LeftJoycon.ButtonSl.ToString();
                    _lSr.Label = keyboardConfig.LeftJoycon.ButtonSr.ToString();
                    _rStickUp.Label = keyboardConfig.RightJoyconStick.StickUp.ToString();
                    _rStickDown.Label = keyboardConfig.RightJoyconStick.StickDown.ToString();
                    _rStickLeft.Label = keyboardConfig.RightJoyconStick.StickLeft.ToString();
                    _rStickRight.Label = keyboardConfig.RightJoyconStick.StickRight.ToString();
                    _rStickButton.Label = keyboardConfig.RightJoyconStick.StickButton.ToString();
                    _a.Label = keyboardConfig.RightJoycon.ButtonA.ToString();
                    _b.Label = keyboardConfig.RightJoycon.ButtonB.ToString();
                    _x.Label = keyboardConfig.RightJoycon.ButtonX.ToString();
                    _y.Label = keyboardConfig.RightJoycon.ButtonY.ToString();
                    _plus.Label = keyboardConfig.RightJoycon.ButtonPlus.ToString();
                    _r.Label = keyboardConfig.RightJoycon.ButtonR.ToString();
                    _zR.Label = keyboardConfig.RightJoycon.ButtonZr.ToString();
                    _rSl.Label = keyboardConfig.RightJoycon.ButtonSl.ToString();
                    _rSr.Label = keyboardConfig.RightJoycon.ButtonSr.ToString();
                    break;

                case StandardControllerInputConfig controllerConfig:
                    if (!_controllerType.SetActiveId(controllerConfig.ControllerType.ToString()))
                    {
                        _controllerType.SetActiveId(_playerIndex == PlayerIndex.Handheld
                            ? ControllerType.Handheld.ToString()
                            : ControllerType.ProController.ToString());
                    }

                    _lStick.Label = controllerConfig.LeftJoyconStick.Joystick.ToString();
                    _invertLStickX.Active = controllerConfig.LeftJoyconStick.InvertStickX;
                    _invertLStickY.Active = controllerConfig.LeftJoyconStick.InvertStickY;
                    _rotateL90CW.Active = controllerConfig.LeftJoyconStick.Rotate90CW;
                    _lStickButton.Label = controllerConfig.LeftJoyconStick.StickButton.ToString();
                    _dpadUp.Label = controllerConfig.LeftJoycon.DpadUp.ToString();
                    _dpadDown.Label = controllerConfig.LeftJoycon.DpadDown.ToString();
                    _dpadLeft.Label = controllerConfig.LeftJoycon.DpadLeft.ToString();
                    _dpadRight.Label = controllerConfig.LeftJoycon.DpadRight.ToString();
                    _minus.Label = controllerConfig.LeftJoycon.ButtonMinus.ToString();
                    _l.Label = controllerConfig.LeftJoycon.ButtonL.ToString();
                    _zL.Label = controllerConfig.LeftJoycon.ButtonZl.ToString();
                    _lSl.Label = controllerConfig.LeftJoycon.ButtonSl.ToString();
                    _lSr.Label = controllerConfig.LeftJoycon.ButtonSr.ToString();
                    _rStick.Label = controllerConfig.RightJoyconStick.Joystick.ToString();
                    _invertRStickX.Active = controllerConfig.RightJoyconStick.InvertStickX;
                    _invertRStickY.Active = controllerConfig.RightJoyconStick.InvertStickY;
                    _rotateR90CW.Active = controllerConfig.RightJoyconStick.Rotate90CW;
                    _rStickButton.Label = controllerConfig.RightJoyconStick.StickButton.ToString();
                    _a.Label = controllerConfig.RightJoycon.ButtonA.ToString();
                    _b.Label = controllerConfig.RightJoycon.ButtonB.ToString();
                    _x.Label = controllerConfig.RightJoycon.ButtonX.ToString();
                    _y.Label = controllerConfig.RightJoycon.ButtonY.ToString();
                    _plus.Label = controllerConfig.RightJoycon.ButtonPlus.ToString();
                    _r.Label = controllerConfig.RightJoycon.ButtonR.ToString();
                    _zR.Label = controllerConfig.RightJoycon.ButtonZr.ToString();
                    _rSl.Label = controllerConfig.RightJoycon.ButtonSl.ToString();
                    _rSr.Label = controllerConfig.RightJoycon.ButtonSr.ToString();
                    _controllerStrongRumble.Value = controllerConfig.Rumble.StrongRumble;
                    _controllerWeakRumble.Value = controllerConfig.Rumble.WeakRumble;
                    _enableRumble.Active = controllerConfig.Rumble.EnableRumble;
                    _controllerDeadzoneLeft.Value = controllerConfig.DeadzoneLeft;
                    _controllerDeadzoneRight.Value = controllerConfig.DeadzoneRight;
                    _controllerRangeLeft.Value = controllerConfig.RangeLeft;
                    _controllerRangeRight.Value = controllerConfig.RangeRight;
                    _controllerTriggerThreshold.Value = controllerConfig.TriggerThreshold;
                    _sensitivity.Value = controllerConfig.Motion.Sensitivity;
                    _gyroDeadzone.Value = controllerConfig.Motion.GyroDeadzone;
                    _enableMotion.Active = controllerConfig.Motion.EnableMotion;
                    _enableCemuHook.Active = controllerConfig.Motion.MotionBackend == MotionInputBackendType.CemuHook;

                    // If both stick ranges are 0 (usually indicative of an outdated profile load) then both sticks will be set to 1.0.
                    if (_controllerRangeLeft.Value <= 0.0 && _controllerRangeRight.Value <= 0.0)
                    {
                        _controllerRangeLeft.Value = 1.0;
                        _controllerRangeRight.Value = 1.0;

                        Logger.Info?.Print(LogClass.Application, $"{config.PlayerIndex} stick range reset. Save the profile now to update your configuration");
                    }

                    if (controllerConfig.Motion is CemuHookMotionConfigController cemuHookMotionConfig)
                    {
                        _slotNumber.Value = cemuHookMotionConfig.Slot;
                        _altSlotNumber.Value = cemuHookMotionConfig.AltSlot;
                        _mirrorInput.Active = cemuHookMotionConfig.MirrorInput;
                        _dsuServerHost.Buffer.Text = cemuHookMotionConfig.DsuServerHost;
                        _dsuServerPort.Buffer.Text = cemuHookMotionConfig.DsuServerPort.ToString();
                    }

                    break;
            }
        }

        private InputConfig GetValues()
        {
            if (_inputDevice.ActiveId.StartsWith("keyboard"))
            {
#pragma warning disable CA1806, IDE0055 // Disable formatting
                Enum.TryParse(_lStickUp.Label,     out Key lStickUp);
                Enum.TryParse(_lStickDown.Label,   out Key lStickDown);
                Enum.TryParse(_lStickLeft.Label,   out Key lStickLeft);
                Enum.TryParse(_lStickRight.Label,  out Key lStickRight);
                Enum.TryParse(_lStickButton.Label, out Key lStickButton);
                Enum.TryParse(_dpadUp.Label,       out Key lDPadUp);
                Enum.TryParse(_dpadDown.Label,     out Key lDPadDown);
                Enum.TryParse(_dpadLeft.Label,     out Key lDPadLeft);
                Enum.TryParse(_dpadRight.Label,    out Key lDPadRight);
                Enum.TryParse(_minus.Label,        out Key lButtonMinus);
                Enum.TryParse(_l.Label,            out Key lButtonL);
                Enum.TryParse(_zL.Label,           out Key lButtonZl);
                Enum.TryParse(_lSl.Label,          out Key lButtonSl);
                Enum.TryParse(_lSr.Label,          out Key lButtonSr);

                Enum.TryParse(_rStickUp.Label,     out Key rStickUp);
                Enum.TryParse(_rStickDown.Label,   out Key rStickDown);
                Enum.TryParse(_rStickLeft.Label,   out Key rStickLeft);
                Enum.TryParse(_rStickRight.Label,  out Key rStickRight);
                Enum.TryParse(_rStickButton.Label, out Key rStickButton);
                Enum.TryParse(_a.Label,            out Key rButtonA);
                Enum.TryParse(_b.Label,            out Key rButtonB);
                Enum.TryParse(_x.Label,            out Key rButtonX);
                Enum.TryParse(_y.Label,            out Key rButtonY);
                Enum.TryParse(_plus.Label,         out Key rButtonPlus);
                Enum.TryParse(_r.Label,            out Key rButtonR);
                Enum.TryParse(_zR.Label,           out Key rButtonZr);
                Enum.TryParse(_rSl.Label,          out Key rButtonSl);
                Enum.TryParse(_rSr.Label,          out Key rButtonSr);
#pragma warning restore CA1806, IDE0055

                return new StandardKeyboardInputConfig
                {
                    Backend = InputBackendType.WindowKeyboard,
                    Version = InputConfig.CurrentVersion,
                    Id = _inputDevice.ActiveId.Split("/")[1],
                    ControllerType = Enum.Parse<ControllerType>(_controllerType.ActiveId),
                    PlayerIndex = _playerIndex,
                    LeftJoycon = new LeftJoyconCommonConfig<Key>
                    {
                        ButtonMinus = lButtonMinus,
                        ButtonL = lButtonL,
                        ButtonZl = lButtonZl,
                        ButtonSl = lButtonSl,
                        ButtonSr = lButtonSr,
                        DpadUp = lDPadUp,
                        DpadDown = lDPadDown,
                        DpadLeft = lDPadLeft,
                        DpadRight = lDPadRight,
                    },
                    LeftJoyconStick = new JoyconConfigKeyboardStick<Key>
                    {
                        StickUp = lStickUp,
                        StickDown = lStickDown,
                        StickLeft = lStickLeft,
                        StickRight = lStickRight,
                        StickButton = lStickButton,
                    },
                    RightJoycon = new RightJoyconCommonConfig<Key>
                    {
                        ButtonA = rButtonA,
                        ButtonB = rButtonB,
                        ButtonX = rButtonX,
                        ButtonY = rButtonY,
                        ButtonPlus = rButtonPlus,
                        ButtonR = rButtonR,
                        ButtonZr = rButtonZr,
                        ButtonSl = rButtonSl,
                        ButtonSr = rButtonSr,
                    },
                    RightJoyconStick = new JoyconConfigKeyboardStick<Key>
                    {
                        StickUp = rStickUp,
                        StickDown = rStickDown,
                        StickLeft = rStickLeft,
                        StickRight = rStickRight,
                        StickButton = rStickButton,
                    },
                };
            }

            if (_inputDevice.ActiveId.StartsWith("controller"))
            {
#pragma warning disable CA1806, IDE0055 // Disable formatting
                Enum.TryParse(_lStick.Label,       out ConfigStickInputId   lStick);
                Enum.TryParse(_lStickButton.Label, out ConfigGamepadInputId lStickButton);
                Enum.TryParse(_minus.Label,        out ConfigGamepadInputId lButtonMinus);
                Enum.TryParse(_l.Label,            out ConfigGamepadInputId lButtonL);
                Enum.TryParse(_zL.Label,           out ConfigGamepadInputId lButtonZl);
                Enum.TryParse(_lSl.Label,          out ConfigGamepadInputId lButtonSl);
                Enum.TryParse(_lSr.Label,          out ConfigGamepadInputId lButtonSr);
                Enum.TryParse(_dpadUp.Label,       out ConfigGamepadInputId lDPadUp);
                Enum.TryParse(_dpadDown.Label,     out ConfigGamepadInputId lDPadDown);
                Enum.TryParse(_dpadLeft.Label,     out ConfigGamepadInputId lDPadLeft);
                Enum.TryParse(_dpadRight.Label,    out ConfigGamepadInputId lDPadRight);

                Enum.TryParse(_rStick.Label,       out ConfigStickInputId   rStick);
                Enum.TryParse(_rStickButton.Label, out ConfigGamepadInputId rStickButton);
                Enum.TryParse(_a.Label,            out ConfigGamepadInputId rButtonA);
                Enum.TryParse(_b.Label,            out ConfigGamepadInputId rButtonB);
                Enum.TryParse(_x.Label,            out ConfigGamepadInputId rButtonX);
                Enum.TryParse(_y.Label,            out ConfigGamepadInputId rButtonY);
                Enum.TryParse(_plus.Label,         out ConfigGamepadInputId rButtonPlus);
                Enum.TryParse(_r.Label,            out ConfigGamepadInputId rButtonR);
                Enum.TryParse(_zR.Label,           out ConfigGamepadInputId rButtonZr);
                Enum.TryParse(_rSl.Label,          out ConfigGamepadInputId rButtonSl);
                Enum.TryParse(_rSr.Label,          out ConfigGamepadInputId rButtonSr);

                int.TryParse(_dsuServerPort.Buffer.Text, out int port);
#pragma warning restore CA1806, IDE0055

                MotionConfigController motionConfig;

                if (_enableCemuHook.Active)
                {
                    motionConfig = new CemuHookMotionConfigController
                    {
                        MotionBackend = MotionInputBackendType.CemuHook,
                        EnableMotion = _enableMotion.Active,
                        Sensitivity = (int)_sensitivity.Value,
                        GyroDeadzone = _gyroDeadzone.Value,
                        MirrorInput = _mirrorInput.Active,
                        Slot = (int)_slotNumber.Value,
                        AltSlot = (int)_altSlotNumber.Value,
                        DsuServerHost = _dsuServerHost.Buffer.Text,
                        DsuServerPort = port,
                    };
                }
                else
                {
                    motionConfig = new StandardMotionConfigController
                    {
                        MotionBackend = MotionInputBackendType.GamepadDriver,
                        EnableMotion = _enableMotion.Active,
                        Sensitivity = (int)_sensitivity.Value,
                        GyroDeadzone = _gyroDeadzone.Value,
                    };
                }

                return new StandardControllerInputConfig
                {
                    Backend = InputBackendType.GamepadSDL2,
                    Version = InputConfig.CurrentVersion,
                    Id = _inputDevice.ActiveId.Split("/")[1].Split(" ")[0],
                    ControllerType = Enum.Parse<ControllerType>(_controllerType.ActiveId),
                    PlayerIndex = _playerIndex,
                    DeadzoneLeft = (float)_controllerDeadzoneLeft.Value,
                    DeadzoneRight = (float)_controllerDeadzoneRight.Value,
                    RangeLeft = (float)_controllerRangeLeft.Value,
                    RangeRight = (float)_controllerRangeRight.Value,
                    TriggerThreshold = (float)_controllerTriggerThreshold.Value,
                    LeftJoycon = new LeftJoyconCommonConfig<ConfigGamepadInputId>
                    {
                        ButtonMinus = lButtonMinus,
                        ButtonL = lButtonL,
                        ButtonZl = lButtonZl,
                        ButtonSl = lButtonSl,
                        ButtonSr = lButtonSr,
                        DpadUp = lDPadUp,
                        DpadDown = lDPadDown,
                        DpadLeft = lDPadLeft,
                        DpadRight = lDPadRight,
                    },
                    LeftJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>
                    {
                        InvertStickX = _invertLStickX.Active,
                        Joystick = lStick,
                        InvertStickY = _invertLStickY.Active,
                        StickButton = lStickButton,
                        Rotate90CW = _rotateL90CW.Active,
                    },
                    RightJoycon = new RightJoyconCommonConfig<ConfigGamepadInputId>
                    {
                        ButtonA = rButtonA,
                        ButtonB = rButtonB,
                        ButtonX = rButtonX,
                        ButtonY = rButtonY,
                        ButtonPlus = rButtonPlus,
                        ButtonR = rButtonR,
                        ButtonZr = rButtonZr,
                        ButtonSl = rButtonSl,
                        ButtonSr = rButtonSr,
                    },
                    RightJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>
                    {
                        InvertStickX = _invertRStickX.Active,
                        Joystick = rStick,
                        InvertStickY = _invertRStickY.Active,
                        StickButton = rStickButton,
                        Rotate90CW = _rotateR90CW.Active,
                    },
                    Motion = motionConfig,
                    Rumble = new RumbleConfigController
                    {
                        StrongRumble = (float)_controllerStrongRumble.Value,
                        WeakRumble = (float)_controllerWeakRumble.Value,
                        EnableRumble = _enableRumble.Active,
                    },
                };
            }

            if (!_inputDevice.ActiveId.StartsWith("disabled"))
            {
                GtkDialog.CreateErrorDialog("Invalid data detected in one or more fields; the configuration was not saved.");
            }

            return null;
        }

        private string GetProfileBasePath()
        {
            if (_inputDevice.ActiveId.StartsWith("keyboard"))
            {
                return System.IO.Path.Combine(AppDataManager.ProfilesDirPath, "keyboard");
            }
            else if (_inputDevice.ActiveId.StartsWith("controller"))
            {
                return System.IO.Path.Combine(AppDataManager.ProfilesDirPath, "controller");
            }

            return AppDataManager.ProfilesDirPath;
        }

        //
        // Events
        //
        private void InputDevice_Changed(object sender, EventArgs args)
        {
            SetAvailableOptions();
            SetControllerSpecificFields();

            _selectedGamepad?.Dispose();
            _selectedGamepad = null;

            if (_inputDevice.ActiveId != null)
            {
                SetProfiles();

                string id = GetCurrentGamepadId();

                if (_inputDevice.ActiveId.StartsWith("keyboard"))
                {
                    if (_inputConfig is StandardKeyboardInputConfig)
                    {
                        SetValues(_inputConfig);
                    }

                    if (_mainWindow.InputManager.KeyboardDriver is GTK3KeyboardDriver)
                    {
                        // NOTE: To get input in this window, we need to bind a custom keyboard driver instead of using the InputManager one as the main window isn't focused...
                        _selectedGamepad = _gtk3KeyboardDriver.GetGamepad(id);
                    }
                    else
                    {
                        _selectedGamepad = _mainWindow.InputManager.KeyboardDriver.GetGamepad(id);
                    }
                }
                else if (_inputDevice.ActiveId.StartsWith("controller"))
                {
                    if (_inputConfig is StandardControllerInputConfig)
                    {
                        SetValues(_inputConfig);
                    }

                    _selectedGamepad = _mainWindow.InputManager.GamepadDriver.GetGamepad(id);
                }
            }
        }

        private string GetCurrentGamepadId()
        {
            if (_inputDevice.ActiveId == null || _inputDevice.ActiveId == "disabled")
            {
                return null;
            }

            return _inputDevice.ActiveId.Split("/")[1].Split(" ")[0];
        }

        private void Controller_Changed(object sender, EventArgs args)
        {
            SetControllerSpecificFields();
        }

        private IButtonAssigner CreateButtonAssigner(bool forStick)
        {
            IButtonAssigner assigner;

            if (_inputDevice.ActiveId.StartsWith("keyboard"))
            {
                assigner = new KeyboardKeyAssigner((IKeyboard)_selectedGamepad);
            }
            else if (_inputDevice.ActiveId.StartsWith("controller"))
            {
                assigner = new GamepadButtonAssigner(_selectedGamepad, (float)_controllerTriggerThreshold.Value, forStick);
            }
            else
            {
                throw new Exception("Controller not supported");
            }

            return assigner;
        }

        private void HandleButtonPressed(ToggleButton button, bool forStick)
        {
            if (_isWaitingForInput)
            {
                button.Active = false;

                return;
            }

            _mousePressed = false;

            ButtonPressEvent += MouseClick;

            IButtonAssigner assigner = CreateButtonAssigner(forStick);

            _isWaitingForInput = true;

            // Open GTK3 keyboard for cancel operations
            IKeyboard keyboard = (IKeyboard)_gtk3KeyboardDriver.GetGamepad("0");

            Thread inputThread = new(() =>
            {
                assigner.Initialize();

                while (true)
                {
                    Thread.Sleep(10);
                    assigner.ReadInput();

                    if (_mousePressed || keyboard.IsPressed(Ryujinx.Input.Key.Escape) || assigner.IsAnyButtonPressed() || assigner.ShouldCancel())
                    {
                        break;
                    }
                }

                string pressedButton = ButtonHelper.ToString(assigner.GetPressedButton() ?? new Button(Input.Key.Unknown));

                Application.Invoke(delegate
                {
                    if (_middleMousePressed)
                    {
                        button.Label = "Unbound";
                    }
                    else if (pressedButton != "")
                    {
                        button.Label = pressedButton;
                    }

                    _middleMousePressed = false;

                    ButtonPressEvent -= MouseClick;
                    keyboard.Dispose();

                    button.Active = false;
                    _isWaitingForInput = false;
                });
            })
            {
                Name = "GUI.InputThread",
                IsBackground = true,
            };
            inputThread.Start();
        }

        private void Button_Pressed(object sender, EventArgs args)
        {
            HandleButtonPressed((ToggleButton)sender, false);
        }

        private void ButtonForStick_Pressed(object sender, EventArgs args)
        {
            HandleButtonPressed((ToggleButton)sender, true);
        }

        private void MouseClick(object sender, ButtonPressEventArgs args)
        {
            _mousePressed = true;
            _middleMousePressed = args.Event.Button == 2;
        }

        private void SetProfiles()
        {
            _profile.RemoveAll();

            string basePath = GetProfileBasePath();

            if (!Directory.Exists(basePath))
            {
                Directory.CreateDirectory(basePath);
            }

            if (_inputDevice.ActiveId == null || _inputDevice.ActiveId.Equals("disabled"))
            {
                _profile.Append("default", "None");
            }
            else
            {
                _profile.Append("default", "Default");

                foreach (string profile in Directory.GetFiles(basePath, "*.*", SearchOption.AllDirectories))
                {
                    _profile.Append(System.IO.Path.GetFileName(profile), System.IO.Path.GetFileNameWithoutExtension(profile));
                }
            }

            _profile.SetActiveId("default");
        }

        private void ProfileLoad_Activated(object sender, EventArgs args)
        {
            ((ToggleButton)sender).SetStateFlags(StateFlags.Normal, true);

            if (_inputDevice.ActiveId == "disabled" || _profile.ActiveId == null)
            {
                return;
            }

            InputConfig config = null;
            int pos = _profile.Active;

            if (_profile.ActiveId == "default")
            {
                if (_inputDevice.ActiveId.StartsWith("keyboard"))
                {
                    config = new StandardKeyboardInputConfig
                    {
                        Version = InputConfig.CurrentVersion,
                        Backend = InputBackendType.WindowKeyboard,
                        Id = null,
                        ControllerType = ControllerType.ProController,
                        LeftJoycon = new LeftJoyconCommonConfig<Key>
                        {
                            DpadUp = Key.Up,
                            DpadDown = Key.Down,
                            DpadLeft = Key.Left,
                            DpadRight = Key.Right,
                            ButtonMinus = Key.Minus,
                            ButtonL = Key.E,
                            ButtonZl = Key.Q,
                            ButtonSl = Key.Unbound,
                            ButtonSr = Key.Unbound,
                        },

                        LeftJoyconStick = new JoyconConfigKeyboardStick<Key>
                        {
                            StickUp = Key.W,
                            StickDown = Key.S,
                            StickLeft = Key.A,
                            StickRight = Key.D,
                            StickButton = Key.F,
                        },

                        RightJoycon = new RightJoyconCommonConfig<Key>
                        {
                            ButtonA = Key.Z,
                            ButtonB = Key.X,
                            ButtonX = Key.C,
                            ButtonY = Key.V,
                            ButtonPlus = Key.Plus,
                            ButtonR = Key.U,
                            ButtonZr = Key.O,
                            ButtonSl = Key.Unbound,
                            ButtonSr = Key.Unbound,
                        },

                        RightJoyconStick = new JoyconConfigKeyboardStick<Key>
                        {
                            StickUp = Key.I,
                            StickDown = Key.K,
                            StickLeft = Key.J,
                            StickRight = Key.L,
                            StickButton = Key.H,
                        },
                    };
                }
                else if (_inputDevice.ActiveId.StartsWith("controller"))
                {
                    bool isNintendoStyle = _inputDevice.ActiveText.Contains("Nintendo");

                    config = new StandardControllerInputConfig
                    {
                        Version = InputConfig.CurrentVersion,
                        Backend = InputBackendType.GamepadSDL2,
                        Id = null,
                        ControllerType = ControllerType.JoyconPair,
                        DeadzoneLeft = 0.1f,
                        DeadzoneRight = 0.1f,
                        RangeLeft = 1.0f,
                        RangeRight = 1.0f,
                        TriggerThreshold = 0.5f,
                        LeftJoycon = new LeftJoyconCommonConfig<ConfigGamepadInputId>
                        {
                            DpadUp = ConfigGamepadInputId.DpadUp,
                            DpadDown = ConfigGamepadInputId.DpadDown,
                            DpadLeft = ConfigGamepadInputId.DpadLeft,
                            DpadRight = ConfigGamepadInputId.DpadRight,
                            ButtonMinus = ConfigGamepadInputId.Minus,
                            ButtonL = ConfigGamepadInputId.LeftShoulder,
                            ButtonZl = ConfigGamepadInputId.LeftTrigger,
                            ButtonSl = ConfigGamepadInputId.Unbound,
                            ButtonSr = ConfigGamepadInputId.Unbound,
                        },

                        LeftJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>
                        {
                            Joystick = ConfigStickInputId.Left,
                            StickButton = ConfigGamepadInputId.LeftStick,
                            InvertStickX = false,
                            InvertStickY = false,
                            Rotate90CW = false,
                        },

                        RightJoycon = new RightJoyconCommonConfig<ConfigGamepadInputId>
                        {
                            ButtonA = isNintendoStyle ? ConfigGamepadInputId.A : ConfigGamepadInputId.B,
                            ButtonB = isNintendoStyle ? ConfigGamepadInputId.B : ConfigGamepadInputId.A,
                            ButtonX = isNintendoStyle ? ConfigGamepadInputId.X : ConfigGamepadInputId.Y,
                            ButtonY = isNintendoStyle ? ConfigGamepadInputId.Y : ConfigGamepadInputId.X,
                            ButtonPlus = ConfigGamepadInputId.Plus,
                            ButtonR = ConfigGamepadInputId.RightShoulder,
                            ButtonZr = ConfigGamepadInputId.RightTrigger,
                            ButtonSl = ConfigGamepadInputId.Unbound,
                            ButtonSr = ConfigGamepadInputId.Unbound,
                        },

                        RightJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>
                        {
                            Joystick = ConfigStickInputId.Right,
                            StickButton = ConfigGamepadInputId.RightStick,
                            InvertStickX = false,
                            InvertStickY = false,
                            Rotate90CW = false,
                        },

                        Motion = new StandardMotionConfigController
                        {
                            MotionBackend = MotionInputBackendType.GamepadDriver,
                            EnableMotion = true,
                            Sensitivity = 100,
                            GyroDeadzone = 1,
                        },
                        Rumble = new RumbleConfigController
                        {
                            StrongRumble = 1f,
                            WeakRumble = 1f,
                            EnableRumble = false,
                        },
                    };
                }
            }
            else
            {
                string path = System.IO.Path.Combine(GetProfileBasePath(), _profile.ActiveId);

                if (!File.Exists(path))
                {
                    if (pos >= 0)
                    {
                        _profile.Remove(pos);
                    }

                    return;
                }

                try
                {
                    config = JsonHelper.DeserializeFromFile(path, _serializerContext.InputConfig);
                }
                catch (JsonException) { }
            }

            SetValues(config);
        }

        private void ProfileAdd_Activated(object sender, EventArgs args)
        {
            ((ToggleButton)sender).SetStateFlags(StateFlags.Normal, true);

            if (_inputDevice.ActiveId == "disabled")
            {
                return;
            }

            InputConfig inputConfig = GetValues();
            ProfileDialog profileDialog = new();

            if (inputConfig == null)
            {
                return;
            }

            if (profileDialog.Run() == (int)ResponseType.Ok)
            {
                string path = System.IO.Path.Combine(GetProfileBasePath(), profileDialog.FileName);
                string jsonString = JsonHelper.Serialize(inputConfig, _serializerContext.InputConfig);

                File.WriteAllText(path, jsonString);
            }

            profileDialog.Dispose();

            SetProfiles();
        }

        private void ProfileRemove_Activated(object sender, EventArgs args)
        {
            ((ToggleButton)sender).SetStateFlags(StateFlags.Normal, true);

            if (_inputDevice.ActiveId == "disabled" || _profile.ActiveId == "default" || _profile.ActiveId == null)
            {
                return;
            }

            MessageDialog confirmDialog = GtkDialog.CreateConfirmationDialog("Deleting Profile", "This action is irreversible, are you sure you want to continue?");

            if (confirmDialog.Run() == (int)ResponseType.Yes)
            {
                string path = System.IO.Path.Combine(GetProfileBasePath(), _profile.ActiveId);

                if (File.Exists(path))
                {
                    File.Delete(path);
                }

                SetProfiles();
            }
        }

        private void SaveToggle_Activated(object sender, EventArgs args)
        {
            InputConfig inputConfig = GetValues();

            var newConfig = new List<InputConfig>();
            newConfig.AddRange(ConfigurationState.Instance.Hid.InputConfig.Value);

            if (_inputConfig == null && inputConfig != null)
            {
                newConfig.Add(inputConfig);
            }
            else
            {
                if (_inputDevice.ActiveId == "disabled")
                {
                    newConfig.Remove(_inputConfig);
                }
                else if (inputConfig != null)
                {
                    int index = newConfig.IndexOf(_inputConfig);

                    newConfig[index] = inputConfig;
                }
            }

            _mainWindow.RendererWidget?.NpadManager.ReloadConfiguration(newConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse);

            // Atomically replace and signal input change.
            // NOTE: Do not modify InputConfig.Value directly as other code depends on the on-change event.
            ConfigurationState.Instance.Hid.InputConfig.Value = newConfig;

            ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);

            Dispose();
        }

        private void CloseToggle_Activated(object sender, EventArgs args)
        {
            Dispose();
        }
    }
}
