// ENABLE_VR is not defined on Game Core but the assembly is available with limited features when the XR module is enabled.
#if UNITY_INPUT_SYSTEM_ENABLE_XR && (ENABLE_VR || UNITY_GAMECORE) || PACKAGE_DOCS_GENERATION
using System;
using System.Collections.Generic;
using UnityEngine.InputSystem.Layouts;
using UnityEngine.InputSystem.Controls;
using UnityEngine.XR;

namespace UnityEngine.InputSystem.XR
{
    /// <summary>
    /// A set of static utilities for registering XR Input Devices externally.
    /// </summary>
    public static class XRUtilities
    {
        /// <summary>
        /// A simple Regex pattern that allows InputDeviceMatchers to match to any version of the XRInput interface.
        /// </summary>
        public const string InterfaceMatchAnyVersion = "^(XRInput)";

        /// <summary>
        /// The initial, now deprecated interface for XRInput.  This version handles button packing for Android differently from current.
        /// </summary>
        public const string InterfaceV1 = "XRInput";

        /// <summary>
        /// The current interface code sent with devices to identify as XRInput devices.
        /// </summary>
        public const string InterfaceCurrent = "XRInputV1";
    }

    // Sync to UnityXRInputFeatureType in IUnityXRInput.h
    /// <summary>
    /// The type of data a <see cref="XRFeatureDescriptor"/> exposes.
    /// </summary>
    public enum FeatureType
    {
        Custom = 0,
        Binary,
        DiscreteStates,
        Axis1D,
        Axis2D,
        Axis3D,
        Rotation,
        Hand,
        Bone,
        Eyes
    }

    /// <summary>
    /// Contextual strings that identify the contextual, cross-platform use that a feature represents.  <see cref="UnityEngine.XR.CommonUsages"/> for a list of unity's built-in shared usages.
    /// </summary>
#pragma warning disable 0649
    [Serializable]
    public struct UsageHint
    {
        public string content;
    }

    //Sync to XRInputFeatureDefinition in XRInputDeviceDefinition.h
    /// <summary>
    /// Describes an individual input on a device, such as a trackpad, or button, or trigger.
    /// </summary>
    [Serializable]
    public struct XRFeatureDescriptor
    {
        /// <summary>
        /// The name of the feature.
        /// </summary>
        public string name;
        /// <summary>
        /// The uses that this feature should represent, such as trigger, or grip, or touchpad.
        /// </summary>
        public List<UsageHint> usageHints;
        /// <summary>
        /// The type of data this feature exposes.
        /// </summary>
        public FeatureType featureType;
        /// <summary>
        /// The overall size of the feature.  This is only filled in when the <see cref="featureType"/> is <see cref="FeatureType.Custom"/>.
        /// </summary>
        public uint customSize;
    }

    //Sync to XRInputDeviceDefinition in XRInputDeviceDefinition.h
    /// <summary>
    /// Describes an input device: what it can do and how it should be used.  These are reported during device connection, and help identify devices and map input data to the right controls.
    /// </summary>
    [Serializable]
    public class XRDeviceDescriptor
    {
        /// <summary>
        /// The name of the device.
        /// </summary>
        public string deviceName;
        /// <summary>
        /// The manufacturer of the device.
        /// </summary>
        public string manufacturer;
        /// <summary>
        /// The serial number of the device.  An empty string if no serial number is available.
        /// </summary>
        public string serialNumber;
        /// <summary>
        /// The capabilities of the device, used to help filter and identify devices that server a certain purpose (e.g. controller, or headset, or hardware tracker).
        /// </summary>
        public InputDeviceCharacteristics characteristics;
        /// <summary>
        /// The underlying deviceId, this can be used with <see cref="UnityEngine.XR.InputDevices"/> to create a device.
        /// </summary>
        public int deviceId;
        /// <summary>
        /// A list of all input features.  <seealso cref="XRFeatureDescriptor"/>
        /// </summary>
        public List<XRFeatureDescriptor> inputFeatures;

        /// <summary>
        /// Converts this structure to a JSON string.
        /// </summary>
        /// <returns></returns>
        public string ToJson()
        {
            return JsonUtility.ToJson(this);
        }

        /// <summary>
        /// Converts a json string to a new <see cref="XRDeviceDescriptor"/>.
        /// </summary>
        /// <param name="json">The JSON string containing <see cref="XRDeviceDescriptor"/> data.</param>
        /// <returns>A new <see cref="XRDeviceDescriptor"/></returns>
        public static XRDeviceDescriptor FromJson(string json)
        {
            return JsonUtility.FromJson<XRDeviceDescriptor>(json);
        }
    }

    /// <summary>
    /// Represents a 3 dimensional, tracked bone within a hierarchy of other bones.
    /// </summary>
    public struct Bone
    {
        /// <summary>
        /// The index with the device's controls array where the parent bone resides.
        /// </summary>
        public uint parentBoneIndex { get; set; }
        /// <summary>
        /// The tracked position of the bone.
        /// </summary>
        public Vector3 position { get; set; }
        /// <summary>
        /// The tracked rotation of the bone.
        /// </summary>
        public Quaternion rotation { get; set; }
    }

    /// <summary>
    /// Represents a pair of tracked eyes.
    /// </summary>
    public struct Eyes
    {
        /// <summary>
        /// The tracked position of the left eye.
        /// </summary>
        public Vector3 leftEyePosition { get; set; }
        /// <summary>
        /// The tracked rotation of the left eye.
        /// </summary>
        public Quaternion leftEyeRotation { get; set; }
        /// <summary>
        /// The tracked position of the right eye.
        /// </summary>
        public Vector3 rightEyePosition { get; set; }
        /// <summary>
        /// The tracked rotation of the right eye.
        /// </summary>
        public Quaternion rightEyeRotation { get; set; }
        /// <summary>
        /// The point in 3D space that the pair of eyes is looking.
        /// </summary>
        public Vector3 fixationPoint { get; set; }
        /// <summary>
        /// The amount [0-1] the left eye is open or closed.  1.0 is fully open.
        /// </summary>
        public float leftEyeOpenAmount { get; set; }
        /// <summary>
        /// The amount [0-1] the right eye is open or closed.  1.0 is fully open.
        /// </summary>
        public float rightEyeOpenAmount { get; set; }
    }

    public class BoneControl : InputControl<Bone>
    {
        [InputControl(offset = 0, displayName = "parentBoneIndex")]
        public IntegerControl parentBoneIndex { get; private set; }
        [InputControl(offset = 4, displayName = "Position")]
        public Vector3Control position { get; private set; }
        [InputControl(offset = 16, displayName = "Rotation")]
        public QuaternionControl rotation { get; private set; }

        protected override void FinishSetup()
        {
            parentBoneIndex = GetChildControl<IntegerControl>("parentBoneIndex");
            position = GetChildControl<Vector3Control>("position");
            rotation = GetChildControl<QuaternionControl>("rotation");

            base.FinishSetup();
        }

        public override unsafe Bone ReadUnprocessedValueFromState(void* statePtr)
        {
            return new Bone()
            {
                parentBoneIndex = (uint)parentBoneIndex.ReadUnprocessedValueFromState(statePtr),
                position = position.ReadUnprocessedValueFromState(statePtr),
                rotation = rotation.ReadUnprocessedValueFromState(statePtr)
            };
        }

        public override unsafe void WriteValueIntoState(Bone value, void* statePtr)
        {
            parentBoneIndex.WriteValueIntoState((int)value.parentBoneIndex, statePtr);
            position.WriteValueIntoState(value.position, statePtr);
            rotation.WriteValueIntoState(value.rotation, statePtr);
        }
    }

    public class EyesControl : InputControl<Eyes>
    {
        [InputControl(offset = 0, displayName = "LeftEyePosition")]
        public Vector3Control leftEyePosition { get; private set; }
        [InputControl(offset = 12, displayName = "LeftEyeRotation")]
        public QuaternionControl leftEyeRotation { get; private set; }
        [InputControl(offset = 28, displayName = "RightEyePosition")]
        public Vector3Control rightEyePosition { get; private set; }
        [InputControl(offset = 40, displayName = "RightEyeRotation")]
        public QuaternionControl rightEyeRotation { get; private set; }
        [InputControl(offset = 56, displayName = "FixationPoint")]
        public Vector3Control fixationPoint { get; private set; }
        [InputControl(offset = 68, displayName = "LeftEyeOpenAmount")]
        public AxisControl leftEyeOpenAmount { get; private set; }
        [InputControl(offset = 72, displayName = "RightEyeOpenAmount")]
        public AxisControl rightEyeOpenAmount { get; private set; }

        protected override void FinishSetup()
        {
            leftEyePosition = GetChildControl<Vector3Control>("leftEyePosition");
            leftEyeRotation = GetChildControl<QuaternionControl>("leftEyeRotation");
            rightEyePosition = GetChildControl<Vector3Control>("rightEyePosition");
            rightEyeRotation = GetChildControl<QuaternionControl>("rightEyeRotation");
            fixationPoint = GetChildControl<Vector3Control>("fixationPoint");
            leftEyeOpenAmount = GetChildControl<AxisControl>("leftEyeOpenAmount");
            rightEyeOpenAmount = GetChildControl<AxisControl>("rightEyeOpenAmount");

            base.FinishSetup();
        }

        public override unsafe Eyes ReadUnprocessedValueFromState(void* statePtr)
        {
            return new Eyes()
            {
                leftEyePosition = leftEyePosition.ReadUnprocessedValueFromState(statePtr),
                leftEyeRotation = leftEyeRotation.ReadUnprocessedValueFromState(statePtr),
                rightEyePosition = rightEyePosition.ReadUnprocessedValueFromState(statePtr),
                rightEyeRotation = rightEyeRotation.ReadUnprocessedValueFromState(statePtr),
                fixationPoint = fixationPoint.ReadUnprocessedValueFromState(statePtr),
                leftEyeOpenAmount = leftEyeOpenAmount.ReadUnprocessedValueFromState(statePtr),
                rightEyeOpenAmount = rightEyeOpenAmount.ReadUnprocessedValueFromState(statePtr)
            };
        }

        public override unsafe void WriteValueIntoState(Eyes value, void* statePtr)
        {
            leftEyePosition.WriteValueIntoState(value.leftEyePosition, statePtr);
            leftEyeRotation.WriteValueIntoState(value.leftEyeRotation, statePtr);
            rightEyePosition.WriteValueIntoState(value.rightEyePosition, statePtr);
            rightEyeRotation.WriteValueIntoState(value.rightEyeRotation, statePtr);
            fixationPoint.WriteValueIntoState(value.fixationPoint, statePtr);
            leftEyeOpenAmount.WriteValueIntoState(value.leftEyeOpenAmount, statePtr);
            rightEyeOpenAmount.WriteValueIntoState(value.rightEyeOpenAmount, statePtr);
        }
    }
#pragma warning restore 0649

    /// <summary>
    /// A small helper class to aid in initializing and registering XR devices and layout builders.
    /// </summary>
#if UNITY_DISABLE_DEFAULT_INPUT_PLUGIN_INITIALIZATION
    public
#else
    internal
#endif
    static class XRSupport
    {
        /// <summary>
        /// Registers all initial templates and the generalized layout builder with the InputSystem.
        /// </summary>
        public static void Initialize()
        {
#if !UNITY_FORCE_INPUTSYSTEM_XR_OFF
            InputSystem.RegisterLayout<PoseControl>("Pose");
            InputSystem.RegisterLayout<BoneControl>("Bone");
            InputSystem.RegisterLayout<EyesControl>("Eyes");

            InputSystem.RegisterLayout<XRHMD>();
            InputSystem.RegisterLayout<XRController>();

            InputSystem.onFindLayoutForDevice += XRLayoutBuilder.OnFindLayoutForDevice;

            // Built-in layouts replaced by the com.unity.xr.windowsmr package.
#if !DISABLE_BUILTIN_INPUT_SYSTEM_WINDOWSMR
            InputSystem.RegisterLayout<UnityEngine.XR.WindowsMR.Input.WMRHMD>(
                matches: new InputDeviceMatcher()
                    .WithInterface(XRUtilities.InterfaceMatchAnyVersion)
                    .WithProduct("(Windows Mixed Reality HMD)|(Microsoft HoloLens)|(^(WindowsMR Headset))")
            );
            InputSystem.RegisterLayout<UnityEngine.XR.WindowsMR.Input.WMRSpatialController>(
                matches: new InputDeviceMatcher()
                    .WithInterface(XRUtilities.InterfaceMatchAnyVersion)
                    .WithProduct(@"(^(Spatial Controller))|(^(OpenVR Controller\(WindowsMR))")
            );
            InputSystem.RegisterLayout<UnityEngine.XR.WindowsMR.Input.HololensHand>(
                matches: new InputDeviceMatcher()
                    .WithInterface(XRUtilities.InterfaceMatchAnyVersion)
                    .WithProduct(@"(^(Hand -))")
            );
#endif

            // Built-in layouts replaced by the com.unity.xr.oculus package.
#if !DISABLE_BUILTIN_INPUT_SYSTEM_OCULUS
            InputSystem.RegisterLayout<Unity.XR.Oculus.Input.OculusHMD>(
                matches: new InputDeviceMatcher()
                    .WithInterface(XRUtilities.InterfaceMatchAnyVersion)
                    .WithProduct("^(Oculus Rift)|^(Oculus Quest)|^(Oculus Go)"));
            InputSystem.RegisterLayout<Unity.XR.Oculus.Input.OculusTouchController>(
                matches: new InputDeviceMatcher()
                    .WithInterface(XRUtilities.InterfaceMatchAnyVersion)
                    .WithProduct(@"(^(Oculus Touch Controller))|(^(Oculus Quest Controller))"));
            InputSystem.RegisterLayout<Unity.XR.Oculus.Input.OculusRemote>(
                matches: new InputDeviceMatcher()
                    .WithInterface(XRUtilities.InterfaceMatchAnyVersion)
                    .WithProduct(@"Oculus Remote"));
            InputSystem.RegisterLayout<Unity.XR.Oculus.Input.OculusTrackingReference>(
                matches: new InputDeviceMatcher()
                    .WithInterface(XRUtilities.InterfaceMatchAnyVersion)
                    .WithProduct(@"((Tracking Reference)|(^(Oculus Rift [a-zA-Z0-9]* \(Camera)))"));

            InputSystem.RegisterLayout<Unity.XR.Oculus.Input.OculusHMDExtended>(
                name: "GearVR",
                matches: new InputDeviceMatcher()
                    .WithInterface(XRUtilities.InterfaceMatchAnyVersion)
                    .WithProduct("Oculus HMD"));
            InputSystem.RegisterLayout<Unity.XR.Oculus.Input.GearVRTrackedController>(
                matches: new InputDeviceMatcher()
                    .WithInterface(XRUtilities.InterfaceMatchAnyVersion)
                    .WithProduct("^(Oculus Tracked Remote)"));
#endif

            // Built-in layouts replaced by the com.unity.xr.googlevr package.
#if !DISABLE_BUILTIN_INPUT_SYSTEM_GOOGLEVR
            InputSystem.RegisterLayout<Unity.XR.GoogleVr.DaydreamHMD>(
                matches: new InputDeviceMatcher()
                    .WithInterface(XRUtilities.InterfaceMatchAnyVersion)
                    .WithProduct("Daydream HMD"));
            InputSystem.RegisterLayout<Unity.XR.GoogleVr.DaydreamController>(
                matches: new InputDeviceMatcher()
                    .WithInterface(XRUtilities.InterfaceMatchAnyVersion)
                    .WithProduct("^(Daydream Controller)"));
#endif

            // Built-in layouts replaced by the com.unity.xr.openvr package.
#if !DISABLE_BUILTIN_INPUT_SYSTEM_OPENVR
            InputSystem.RegisterLayout<Unity.XR.OpenVR.OpenVRHMD>(
                matches: new InputDeviceMatcher()
                    .WithInterface(XRUtilities.InterfaceMatchAnyVersion)
                    .WithProduct("^(OpenVR Headset)|^(Vive Pro)")
            );
            InputSystem.RegisterLayout<Unity.XR.OpenVR.OpenVRControllerWMR>(
                matches: new InputDeviceMatcher()
                    .WithInterface(XRUtilities.InterfaceMatchAnyVersion)
                    .WithProduct("^(OpenVR Controller\\(WindowsMR)")
            );
            InputSystem.RegisterLayout<Unity.XR.OpenVR.ViveWand>(
                matches: new InputDeviceMatcher()
                    .WithInterface(XRUtilities.InterfaceMatchAnyVersion)
                    .WithManufacturer("HTC")
                    .WithProduct(@"^(OpenVR Controller\(((Vive. Controller)|(VIVE. Controller)|(Vive Controller)))")
            );
            InputSystem.RegisterLayout<Unity.XR.OpenVR.OpenVROculusTouchController>(
                matches: new InputDeviceMatcher()
                    .WithInterface(XRUtilities.InterfaceMatchAnyVersion)
                    .WithProduct(@"^(OpenVR Controller\(Oculus)")
            );

            InputSystem.RegisterLayout<Unity.XR.OpenVR.ViveTracker>(
                matches: new InputDeviceMatcher()
                    .WithInterface(XRUtilities.InterfaceMatchAnyVersion)
                    .WithManufacturer("HTC")
                    .WithProduct(@"^(VIVE Tracker)")
            );
            InputSystem.RegisterLayout<Unity.XR.OpenVR.HandedViveTracker>(
                matches: new InputDeviceMatcher()
                    .WithInterface(XRUtilities.InterfaceMatchAnyVersion)
                    .WithManufacturer("HTC")
                    .WithProduct(@"^(OpenVR Controller\(VIVE Tracker)")
            );
            InputSystem.RegisterLayout<Unity.XR.OpenVR.ViveLighthouse>(
                matches: new InputDeviceMatcher()
                    .WithInterface(XRUtilities.InterfaceMatchAnyVersion)
                    .WithManufacturer("HTC")
                    .WithProduct(@"^(HTC V2-XD/XE)")
            );
#endif
#endif
        }
    }
}
#endif