using Gtk;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Common.Configuration.Hid.Keyboard;
using Ryujinx.Common.Utilities;
using Ryujinx.Ui.Common.Configuration;
using Ryujinx.Input;
using Ryujinx.Input.GTK3;
using Ryujinx.Ui.Widgets;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text.Json;
using System.Threading;

using GUI = Gtk.Builder.ObjectAttribute;
using Key = Ryujinx.Common.Configuration.Hid.Key;

using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId;
using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId;
using Ryujinx.Common.Configuration.Hid.Controller.Motion;
using Ryujinx.Common.Logging;
using Ryujinx.Input.Assigner;

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

        private bool _isWaitingForInput;

#pragma warning disable CS0649, IDE0044
        [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 MainWindow _mainWindow;
        private IGamepadDriver _gtk3KeyboardDriver;
        private IGamepad _selectedGamepad;
        private bool _mousePressed;
        private bool _middleMousePressed;

        public ControllerWindow(MainWindow mainWindow, PlayerIndex controllerId) : this(mainWindow, new Builder("Ryujinx.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;

            if (_mainWindow.RendererWidget != null)
            {
                _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;

            if (_mainWindow.RendererWidget != null)
            {
                _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"))
            {
                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);

                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"))
            {
                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);

                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 Thread(() =>
            {
                assigner.Initialize();

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

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

                string pressedButton = assigner.GetPressedButton();

                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;
                });
            });

            inputThread.Name = "GUI.InputThread";
            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
                {
                    using (Stream stream = File.OpenRead(path))
                    {
                        config = JsonHelper.Deserialize<InputConfig>(stream);
                    }
                }
                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 ProfileDialog();

            if (inputConfig == null) return;

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

                jsonString = JsonHelper.Serialize(inputConfig, true);

                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;
                }
            }

            if (_mainWindow.RendererWidget != null)
            {
                _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();
        }
    }
}