using System.Linq;
using UnityEngine.InputSystem.Utilities;
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine.InputSystem.Editor;
using UnityEngine.InputSystem.HID.Editor;
#endif

namespace UnityEngine.InputSystem.HID
{
    using ShouldCreateHIDCallback = System.Func<HID.HIDDeviceDescriptor, bool?>;

    /// <summary>
    /// Adds support for generic HID devices to the input system.
    /// </summary>
    /// <remarks>
    /// Even without this module, HIDs can be used on platforms where we
    /// support HID has a native backend (Windows and OSX, at the moment).
    /// However, each supported HID requires a layout specifically targeting
    /// it as a product.
    ///
    /// What this module adds is the ability to turn any HID with usable
    /// controls into an InputDevice. It will make a best effort to figure
    /// out a suitable class for the device and will use the HID elements
    /// present in the HID report descriptor to populate the device.
    ///
    /// If there is an existing product-specific layout for a HID, it will
    /// take precedence and HIDSupport will leave the device alone.
    /// </remarks>
    public static class HIDSupport
    {
        /// <summary>
        /// A pair of HID usage page and HID usage number.
        /// </summary>
        /// <remarks>
        /// Used to describe a HID usage for the <see cref="supportedHIDUsages"/> property.
        /// </remarks>
        public struct HIDPageUsage
        {
            /// <summary>
            /// The usage page.
            /// </summary>
            public HID.UsagePage page;

            /// <summary>
            /// A number specifying the usage on the usage page.
            /// </summary>
            public int usage;

            /// <summary>
            /// Create a HIDPageUsage struct by specifying a page and usage.
            /// </summary>
            public HIDPageUsage(HID.UsagePage page, int usage)
            {
                this.page = page;
                this.usage = usage;
            }

            /// <summary>
            /// Create a HIDPageUsage struct from the GenericDesktop usage page by specifying the usage.
            /// </summary>
            public HIDPageUsage(HID.GenericDesktop usage)
            {
                page = HID.UsagePage.GenericDesktop;
                this.usage = (int)usage;
            }
        }

        private static HIDPageUsage[] s_SupportedHIDUsages;

        /// <summary>
        /// An array of HID usages the input is configured to support.
        /// </summary>
        /// <remarks>
        /// The input system will only create <see cref="InputDevice"/>s for HIDs with usages
        /// listed in this array. Any other HID will be ignored. This saves the input system from
        /// spending resources on creating layouts and devices for HIDs which are not supported or
        /// not usable for game input.
        ///
        /// By default, this includes only <see cref="HID.GenericDesktop.Joystick"/>,
        /// <see cref="HID.GenericDesktop.Gamepad"/> and <see cref="HID.GenericDesktop.MultiAxisController"/>,
        /// but you can set this property to include any other HID usages.
        ///
        /// Note that currently on macOS, the only HID usages which can be enabled are
        /// <see cref="HID.GenericDesktop.Joystick"/>, <see cref="HID.GenericDesktop.Gamepad"/>,
        /// <see cref="HID.GenericDesktop.MultiAxisController"/>, <see cref="HID.GenericDesktop.TabletPCControls"/>,
        /// and <see cref="HID.GenericDesktop.AssistiveControl"/>.
        /// </remarks>
        public static ReadOnlyArray<HIDPageUsage> supportedHIDUsages
        {
            get => s_SupportedHIDUsages;
            set
            {
                s_SupportedHIDUsages = value.ToArray();

                // Add HIDs we now support.
                InputSystem.s_Manager.AddAvailableDevicesThatAreNowRecognized();

                // Remove HIDs we no longer support.
                for (var i = 0; i < InputSystem.devices.Count; ++i)
                {
                    var device = InputSystem.devices[i];
                    if (device is HID hid && !s_SupportedHIDUsages.Contains(new HIDPageUsage(hid.hidDescriptor.usagePage, hid.hidDescriptor.usage)))
                    {
                        // Remove the entire generated layout. This will also remove all devices based on it.
                        InputSystem.RemoveLayout(device.layout);
                        --i;
                    }
                }
            }
        }

        /// <summary>
        /// Add support for generic HIDs to InputSystem.
        /// </summary>
#if UNITY_DISABLE_DEFAULT_INPUT_PLUGIN_INITIALIZATION
        public
#else
        internal
#endif
        static void Initialize()
        {
            s_SupportedHIDUsages = new[]
            {
                new HIDPageUsage(HID.GenericDesktop.Joystick),
                new HIDPageUsage(HID.GenericDesktop.Gamepad),
                new HIDPageUsage(HID.GenericDesktop.MultiAxisController),
            };

            InputSystem.RegisterLayout<HID>();
            InputSystem.onFindLayoutForDevice += HID.OnFindLayoutForDevice;

            // Add toolbar button to any devices using the "HID" interface. Opens
            // a windows to browse the HID descriptor of the device.
            #if UNITY_EDITOR
            InputDeviceDebuggerWindow.onToolbarGUI +=
                device =>
            {
                if (device.description.interfaceName == HID.kHIDInterface)
                {
                    if (GUILayout.Button(s_HIDDescriptor, EditorStyles.toolbarButton))
                    {
                        HIDDescriptorWindow.CreateOrShowExisting(device.deviceId, device.description);
                    }
                }
            };
            #endif
        }

        #if UNITY_EDITOR
        private static readonly GUIContent s_HIDDescriptor = new GUIContent("HID Descriptor");
        #endif
    }
}
