// Copyright HTC Corporation All Rights Reserved.

using UnityEngine.XR.OpenXR;
using UnityEngine.XR.OpenXR.Features;
using UnityEngine;
using System.Runtime.InteropServices;
using System;
using AOT;

#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.XR.OpenXR.Features;
#endif

namespace VIVE.OpenXR.EyeTracker
{
#if UNITY_EDITOR
    [OpenXRFeature(UiName = "VIVE XR Eye Tracker (Beta)",
        BuildTargetGroups = new[] { BuildTargetGroup.Android, BuildTargetGroup.Standalone },
        Company = "HTC",
        Desc = "Support the eye tracker extension.",
        DocumentationLink = "..\\Documentation",
        OpenxrExtensionStrings = kOpenxrExtensionString,
        Version = "1.0.0",
        FeatureId = featureId)]
#endif
    public class ViveEyeTracker : OpenXRFeature
    {
        const string LOG_TAG = "VIVE.OpenXR.Eye.ViveEyeTracker";
        void DEBUG(string msg) { Debug.Log(LOG_TAG + " " + msg); }
        void WARNING(string msg) { Debug.LogWarning(LOG_TAG + " " + msg); }
        void ERROR(string msg) { Debug.LogError(LOG_TAG + " " + msg); }

        public const string kOpenxrExtensionString = "XR_HTC_eye_tracker";

        /// <summary>
        /// The feature id string. This is used to give the feature a well known id for reference.
        /// </summary>
        public const string featureId = "vive.openxr.feature.eye.tracker";

        #region OpenXR Life Cycle
        private bool m_XrInstanceCreated = false;
        private XrInstance m_XrInstance = 0;
        private static IntPtr xrGetInstanceProcAddr_prev;
        private static IntPtr WaitFrame_prev;
        private static XrFrameWaitInfo m_frameWaitInfo;
        private static XrFrameState m_frameState;
        protected override IntPtr HookGetInstanceProcAddr(IntPtr func)
        {
            UnityEngine.Debug.Log("EXT: registering our own xrGetInstanceProcAddr");
            xrGetInstanceProcAddr_prev = func;
            return Marshal.GetFunctionPointerForDelegate(m_intercept_xrWaitFrame_xrGetInstanceProcAddr);
        }
        [MonoPInvokeCallback(typeof(OpenXRHelper.xrGetInstanceProcAddrDelegate))]
        private static XrResult intercept_xrWaitFrame_xrGetInstanceProcAddr(XrInstance instance, string name, out IntPtr function)
        {
            if (xrGetInstanceProcAddr_prev == null || xrGetInstanceProcAddr_prev == IntPtr.Zero)
            {
                UnityEngine.Debug.LogError("xrGetInstanceProcAddr_prev is null");
                function = IntPtr.Zero;
                return XrResult.XR_ERROR_VALIDATION_FAILURE;
            }

            // Get delegate of old xrGetInstanceProcAddr.
            var xrGetProc = Marshal.GetDelegateForFunctionPointer<OpenXRHelper.xrGetInstanceProcAddrDelegate>(xrGetInstanceProcAddr_prev);
            XrResult result = xrGetProc(instance, name, out function);
            if (name == "xrWaitFrame")
            {
                WaitFrame_prev = function;
                m_intercept_xrWaitFrame = intercepted_xrWaitFrame;
                function = Marshal.GetFunctionPointerForDelegate(m_intercept_xrWaitFrame); ;
                UnityEngine.Debug.Log("Getting xrWaitFrame func");
            }

            return result;

        }
        [MonoPInvokeCallback(typeof(OpenXRHelper.xrWaitFrameDelegate))]
        private static int intercepted_xrWaitFrame(ulong session, ref XrFrameWaitInfo frameWaitInfo, ref XrFrameState frameState)
        {
            // Get delegate of prev xrWaitFrame.
            var xrWaitFrame = Marshal.GetDelegateForFunctionPointer<OpenXRHelper.xrWaitFrameDelegate>(WaitFrame_prev);
            int res = xrWaitFrame(session, ref frameWaitInfo, ref frameState);
            m_frameWaitInfo = frameWaitInfo;
            m_frameState = frameState;
            return res;
        }
        /// <summary>
        /// Called when <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrCreateInstance">xrCreateInstance</see> is done.
        /// </summary>
        /// <param name="xrInstance">The created instance.</param>
        /// <returns>True for valid <see cref="XrInstance">XrInstance</see></returns>
        protected override bool OnInstanceCreate(ulong xrInstance)
        {
            if (!OpenXRRuntime.IsExtensionEnabled(kOpenxrExtensionString))
            {
                WARNING("OnInstanceCreate() " + kOpenxrExtensionString + " is NOT enabled.");
                return false;
            }

            m_XrInstanceCreated = true;
            m_XrInstance = xrInstance;
            DEBUG("OnInstanceCreate() " + m_XrInstance);
            return GetXrFunctionDelegates(m_XrInstance);
        }
        /// <summary>
        /// Called when <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrDestroyInstance">xrDestroyInstance</see> is done.
        /// </summary>
        /// <param name="xrInstance">The instance to destroy.</param>
        protected override void OnInstanceDestroy(ulong xrInstance)
        {
            m_XrInstanceCreated = false;
            m_XrInstance = 0;
            DEBUG("OnInstanceDestroy() " + xrInstance);
        }

        private XrSystemId m_XrSystemId = 0;
        /// <summary>
        /// Called when the <see cref="XrSystemId">XrSystemId</see> retrieved by <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrGetSystem">xrGetSystem</see> is changed.
        /// </summary>
        /// <param name="xrSystem">The system id.</param>
        protected override void OnSystemChange(ulong xrSystem)
        {
            m_XrSystemId = xrSystem;
            DEBUG("OnSystemChange() " + m_XrSystemId);
        }

        private bool m_XrSessionCreated = false;
        private XrSession m_XrSession = 0;
        private bool hasEyeTracker = false;
        private XrEyeTrackerHTC m_EyeTracker = 0;
        /// <summary>
        /// Called when <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrCreateSession">xrCreateSession</see> is done.
        /// </summary>
        /// <param name="xrSession">The created session ID.</param>
        protected override void OnSessionCreate(ulong xrSession)
        {
            m_XrSession = xrSession;
            m_XrSessionCreated = true;
            DEBUG("OnSessionCreate() " + m_XrSession);

            if (CreateEyeTracker()) { DEBUG("OnSessionCreate() m_EyeTracker " + m_EyeTracker); }
        }
        /// <summary>
        /// Called when <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrDestroySession">xrDestroySession</see> is done.
        /// </summary>
        /// <param name="xrSession">The session ID to destroy.</param>
        protected override void OnSessionDestroy(ulong xrSession)
        {
            DEBUG("OnSessionDestroy() " + xrSession);

            // Eye Tracking is binding with xrSession so we destroy the trackers when xrSession is destroyed.
            DestroyEyeTracker();

            m_XrSession = 0;
            m_XrSessionCreated = false;
        }
        #endregion

        #region OpenXR function delegates
        private static readonly OpenXRHelper.xrGetInstanceProcAddrDelegate m_intercept_xrWaitFrame_xrGetInstanceProcAddr
            = new OpenXRHelper.xrGetInstanceProcAddrDelegate(intercept_xrWaitFrame_xrGetInstanceProcAddr);
        private static OpenXRHelper.xrWaitFrameDelegate m_intercept_xrWaitFrame;
        /// xrGetInstanceProcAddr
        OpenXRHelper.xrGetInstanceProcAddrDelegate XrGetInstanceProcAddr;

        /// xrGetSystemProperties
        OpenXRHelper.xrGetSystemPropertiesDelegate xrGetSystemProperties;
        private XrResult GetSystemProperties(ref XrSystemProperties properties)
        {
            if (!m_XrSessionCreated)
            {
                ERROR("GetSystemProperties() XR_ERROR_SESSION_LOST.");
                return XrResult.XR_ERROR_SESSION_LOST;
            }
            if (!m_XrInstanceCreated)
            {
                ERROR("GetSystemProperties() XR_ERROR_INSTANCE_LOST.");
                return XrResult.XR_ERROR_INSTANCE_LOST;
            }

            return xrGetSystemProperties(m_XrInstance, m_XrSystemId, ref properties);
        }

        /// xrDestroySpace
        OpenXRHelper.xrDestroySpaceDelegate xrDestroySpace;
        private XrResult DestroySpace(XrSpace space)
        {
            if (!m_XrSessionCreated)
            {
                ERROR("DestroySpace() XR_ERROR_SESSION_LOST.");
                return XrResult.XR_ERROR_SESSION_LOST;
            }
            if (!m_XrInstanceCreated)
            {
                ERROR("DestroySpace() XR_ERROR_INSTANCE_LOST.");
                return XrResult.XR_ERROR_INSTANCE_LOST;
            }

            return xrDestroySpace(space);
        }

        ViveEyeTrackerHelper.xrCreateEyeTrackerHTCDelegate xrCreateEyeTrackerHTC;
        private XrResult CreateEyeTrackerHTC(ref XrEyeTrackerCreateInfoHTC createInfo, out XrEyeTrackerHTC eyeTracker)
        {
            if (!m_XrSessionCreated)
            {
                ERROR("CreateEyeTrackerHTC() XR_ERROR_SESSION_LOST.");
                eyeTracker = 0;
                return XrResult.XR_ERROR_SESSION_LOST;
            }
            if (!m_XrInstanceCreated)
            {
                ERROR("CreateEyeTrackerHTC() XR_ERROR_INSTANCE_LOST.");
                eyeTracker = 0;
                return XrResult.XR_ERROR_INSTANCE_LOST;
            }

            return xrCreateEyeTrackerHTC(m_XrSession, ref createInfo, out eyeTracker);
        }

        ViveEyeTrackerHelper.xrDestroyEyeTrackerHTCDelegate xrDestroyEyeTrackerHTC;
        private XrResult DestroyEyeTrackerHTC(XrEyeTrackerHTC eyeTracker)
        {
            if (!m_XrSessionCreated)
            {
                ERROR("DestroyEyeTrackerHTC() XR_ERROR_SESSION_LOST.");
                return XrResult.XR_ERROR_SESSION_LOST;
            }
            if (!m_XrInstanceCreated)
            {
                ERROR("DestroyEyeTrackerHTC() XR_ERROR_INSTANCE_LOST.");
                return XrResult.XR_ERROR_INSTANCE_LOST;
            }

            return xrDestroyEyeTrackerHTC(eyeTracker);
        }

        ViveEyeTrackerHelper.xrGetEyeGazeDataHTCDelegate xrGetEyeGazeDataHTC;
        private XrResult GetEyeGazeDataHTC(XrEyeTrackerHTC eyeTracker,ref XrEyeGazeDataInfoHTC gazeInfo, ref XrEyeGazeDataHTC eyeGazes)
        {
            if (!m_XrSessionCreated)
            {
                ERROR("GetEyeGazeDataHTC() XR_ERROR_SESSION_LOST.");
                return XrResult.XR_ERROR_SESSION_LOST;
            }
            if (!m_XrInstanceCreated)
            {
                ERROR("GetEyeGazeDataHTC() XR_ERROR_INSTANCE_LOST.");
                return XrResult.XR_ERROR_INSTANCE_LOST;
            }

            XrResult res =  xrGetEyeGazeDataHTC(eyeTracker,ref gazeInfo,ref eyeGazes);
            return res;
        }
        ViveEyeTrackerHelper.xrGetEyePupilDataHTCDelegate xrGetEyePupilDataHTC;

        private XrResult GetEyePupilDataHTC(XrEyeTrackerHTC eyeTracker,ref XrEyePupilDataInfoHTC pupilDataInfo,ref XrEyePupilDataHTC pupilData)
        {
            if (!m_XrSessionCreated)
            {
                ERROR("GetEyePupilData() XR_ERROR_SESSION_LOST.");
                return XrResult.XR_ERROR_SESSION_LOST;
            }
            if (!m_XrInstanceCreated)
            {
                ERROR("GetEyePupilData() XR_ERROR_INSTANCE_LOST.");
                return XrResult.XR_ERROR_INSTANCE_LOST;
            }
            return xrGetEyePupilDataHTC(eyeTracker,ref pupilDataInfo, ref pupilData);
        }
        ViveEyeTrackerHelper.xrGetEyeGeometricDataHTC xrGetEyeGeometricDataHTC;
        private XrResult GetEyeGeometricDataHTC(XrEyeTrackerHTC eyeTracker,
            ref XrEyeGeometricDataInfoHTC info,
            ref XrEyeGeometricDataHTC eyeGeometricData)
        {
            if (!m_XrSessionCreated)
            {
                ERROR("GetEyeGeometricData() XR_ERROR_SESSION_LOST.");
                return XrResult.XR_ERROR_SESSION_LOST;
            }
            if (!m_XrInstanceCreated)
            {
                ERROR("GetEyeGeometricData() XR_ERROR_INSTANCE_LOST.");
                return XrResult.XR_ERROR_INSTANCE_LOST;
            }
            return xrGetEyeGeometricDataHTC(eyeTracker,ref info, ref eyeGeometricData);
        }

        private bool GetXrFunctionDelegates(XrInstance xrInstance)
        {
            /// xrGetInstanceProcAddr
            if (xrGetInstanceProcAddr != null && xrGetInstanceProcAddr != IntPtr.Zero)
            {
                DEBUG("Get function pointer of xrGetInstanceProcAddr.");
                XrGetInstanceProcAddr = Marshal.GetDelegateForFunctionPointer(
                    xrGetInstanceProcAddr,
                    typeof(OpenXRHelper.xrGetInstanceProcAddrDelegate)) as OpenXRHelper.xrGetInstanceProcAddrDelegate;
            }
            else
            {
                ERROR("xrGetInstanceProcAddr is null");
                return false;
            }

            IntPtr funcPtr = IntPtr.Zero;
            /// xrGetSystemProperties
            if (XrGetInstanceProcAddr(xrInstance, "xrGetSystemProperties", out funcPtr) == XrResult.XR_SUCCESS)
            {
                if (funcPtr != IntPtr.Zero)
                {
                    DEBUG("Get function pointer of xrGetSystemProperties.");
                    xrGetSystemProperties = Marshal.GetDelegateForFunctionPointer(
                        funcPtr,
                        typeof(OpenXRHelper.xrGetSystemPropertiesDelegate)) as OpenXRHelper.xrGetSystemPropertiesDelegate;
                }
            }
            else
            {
                ERROR("xrGetSystemProperties");
                return false;
            }
            /// xrDestroySpace
            if (XrGetInstanceProcAddr(xrInstance, "xrDestroySpace", out funcPtr) == XrResult.XR_SUCCESS)
            {
                if (funcPtr != IntPtr.Zero)
                {
                    DEBUG("Get function pointer of xrDestroySpace.");
                    xrDestroySpace = Marshal.GetDelegateForFunctionPointer(
                        funcPtr,
                        typeof(OpenXRHelper.xrDestroySpaceDelegate)) as OpenXRHelper.xrDestroySpaceDelegate;
                }
            }
            else
            {
                ERROR("xrDestroySpace");
                return false;
            }

            /// xrCreateEyeTrackerHTC
            if (XrGetInstanceProcAddr(xrInstance, "xrCreateEyeTrackerHTC", out funcPtr) == XrResult.XR_SUCCESS)
            {
                if (funcPtr != IntPtr.Zero)
                {
                    DEBUG("Get function pointer of xrCreateEyeTrackerHTC.");
                    xrCreateEyeTrackerHTC = Marshal.GetDelegateForFunctionPointer(
                        funcPtr,
                        typeof(ViveEyeTrackerHelper.xrCreateEyeTrackerHTCDelegate)) as ViveEyeTrackerHelper.xrCreateEyeTrackerHTCDelegate;
                }
            }
            else
            {
                ERROR("xrCreateEyeTrackerHTC");
                return false;
            }
            /// xrDestroyEyeTrackerHTC
            if (XrGetInstanceProcAddr(xrInstance, "xrDestroyEyeTrackerHTC", out funcPtr) == XrResult.XR_SUCCESS)
            {
                if (funcPtr != IntPtr.Zero)
                {
                    DEBUG("Get function pointer of xrDestroyEyeTrackerHTC.");
                    xrDestroyEyeTrackerHTC = Marshal.GetDelegateForFunctionPointer(
                        funcPtr,
                        typeof(ViveEyeTrackerHelper.xrDestroyEyeTrackerHTCDelegate)) as ViveEyeTrackerHelper.xrDestroyEyeTrackerHTCDelegate;
                }
            }
            else
            {
                ERROR("xrDestroyEyeTrackerHTC");
                return false;
            }
            /// xrGetEyeGazeDataHTC
            if (XrGetInstanceProcAddr(xrInstance, "xrGetEyeGazeDataHTC", out funcPtr) == XrResult.XR_SUCCESS)
            {
                if (funcPtr != IntPtr.Zero)
                {
                    DEBUG("Get function pointer of xrGetEyeGazeDataHTC.");
                    xrGetEyeGazeDataHTC = Marshal.GetDelegateForFunctionPointer(
                        funcPtr,
                        typeof(ViveEyeTrackerHelper.xrGetEyeGazeDataHTCDelegate)) as ViveEyeTrackerHelper.xrGetEyeGazeDataHTCDelegate;
                }
            }
            else
            {
                ERROR("xrGetEyeGazeDataHTC");
                return false;
            }
            /// xrGetEyePupilDataHTC
            if (XrGetInstanceProcAddr(xrInstance, "xrGetEyePupilDataHTC", out funcPtr) == XrResult.XR_SUCCESS)
            {
                if (funcPtr != IntPtr.Zero)
                {
                    DEBUG("Get function pointer of xrGetEyePupilDataHTC.");
                    xrGetEyePupilDataHTC = Marshal.GetDelegateForFunctionPointer(
                        funcPtr,
                        typeof(ViveEyeTrackerHelper.xrGetEyePupilDataHTCDelegate)) as ViveEyeTrackerHelper.xrGetEyePupilDataHTCDelegate;
                }
            }
            else
            {
                ERROR("xrGetEyePupilDataHTC");
                return false;
            }
            /// xrGetEyeGeometricDataHTC
            if (XrGetInstanceProcAddr(xrInstance, "xrGetEyeGeometricDataHTC", out funcPtr) == XrResult.XR_SUCCESS)
            {
                if (funcPtr != IntPtr.Zero)
                {
                    DEBUG("Get function pointer of xrGetEyeGeometricDataHTC.");
                    xrGetEyeGeometricDataHTC = Marshal.GetDelegateForFunctionPointer(
                        funcPtr,
                        typeof(ViveEyeTrackerHelper.xrGetEyeGeometricDataHTC)) as ViveEyeTrackerHelper.xrGetEyeGeometricDataHTC;
                }
            }
            else
            {
                ERROR("xrGetEyeGeometricDataHTC");
                return false;
            }

            return true;
        }
        #endregion

        XrSystemEyeTrackingPropertiesHTC eyeTrackingSystemProperties;
        XrSystemProperties systemProperties;
        private bool IsEyeTrackingSupported()
        {
            if (!m_XrSessionCreated)
            {
                ERROR("IsEyeTrackingSupported() session is not created.");
                return false;
            }

            eyeTrackingSystemProperties.type = XrStructureType.XR_TYPE_SYSTEM_EYE_TRACKING_PROPERTIES_HTC;
            systemProperties.type = XrStructureType.XR_TYPE_SYSTEM_PROPERTIES;
            systemProperties.next = Marshal.AllocHGlobal(Marshal.SizeOf(eyeTrackingSystemProperties));

            long offset = 0;
            if (IntPtr.Size == 4)
                offset = systemProperties.next.ToInt32();
            else
                offset = systemProperties.next.ToInt64();

            IntPtr sys_eye_tracking_prop_ptr = new IntPtr(offset);
            Marshal.StructureToPtr(eyeTrackingSystemProperties, sys_eye_tracking_prop_ptr, false);

            if (GetSystemProperties(ref systemProperties) == XrResult.XR_SUCCESS)
            {
                if (IntPtr.Size == 4)
                    offset = systemProperties.next.ToInt32();
                else
                    offset = systemProperties.next.ToInt64();

                sys_eye_tracking_prop_ptr = new IntPtr(offset);
                eyeTrackingSystemProperties = (XrSystemEyeTrackingPropertiesHTC)Marshal.PtrToStructure(sys_eye_tracking_prop_ptr, typeof(XrSystemEyeTrackingPropertiesHTC));

                DEBUG("IsEyeTrackingSupported() XrSystemEyeTrackingPropertiesHTC.supportsEyeTracking: "
                    + eyeTrackingSystemProperties.supportsEyeTracking);

                return (eyeTrackingSystemProperties.supportsEyeTracking > 0);
            }
            else
            {
                ERROR("IsEyeTrackingSupported() GetSystemProperties failed.");
            }

            return false;
        }

        /// <summary>
        /// An application can create an <see cref="XrEyeTrackerHTC">XrEyeTrackerHTC</see> handle using CreateEyeTracker.
        /// </summary>
        /// <param name="createInfo">The <see cref="XrEyeTrackerCreateInfoHTC">XrEyeTrackerCreateInfoHTC</see> used to specify the eye tracker.</param>
        /// <param name="eyeTracker">The returned XrEyeTrackerHTC handle.</param>
        /// <returns>XR_SUCCESS for success.</returns>
        public XrResult CreateEyeTracker(XrEyeTrackerCreateInfoHTC createInfo, out XrEyeTrackerHTC eyeTracker)
        {
            if (hasEyeTracker)
            {
                eyeTracker = m_EyeTracker;
                DEBUG("CreateEyeTracker() m_EyeTracker: " + eyeTracker + " already created before.");
                return XrResult.XR_SUCCESS;
            }

            if (!IsEyeTrackingSupported())
            {
                ERROR("CreateEyeTracker() is NOT supported.");
                eyeTracker = 0;
                return XrResult.XR_ERROR_VALIDATION_FAILURE;
            }

            var result = CreateEyeTrackerHTC(ref createInfo, out eyeTracker);
            DEBUG("CreateEyeTracker() " + result + ", eyeTracker: " + eyeTracker);

            if (result == XrResult.XR_SUCCESS)
            {
                hasEyeTracker = true;
                m_EyeTracker = eyeTracker;
                DEBUG("CreateEyeTracker() m_EyeTracker " + m_EyeTracker);
            }
            return result;
        }
        /// <summary>
        /// An application can create an <see cref="XrEyeTrackerHTC">XrEyeTrackerHTC</see> handle using CreateEyeTracker.
        /// </summary>
        /// <returns>True for success.</returns>
        public bool CreateEyeTracker()
        {
            XrEyeTrackerCreateInfoHTC createInfo = new XrEyeTrackerCreateInfoHTC(
                in_type: XrStructureType.XR_TYPE_EYE_TRACKER_CREATE_INFO_HTC,
                in_next: IntPtr.Zero);

            var result = CreateEyeTracker(createInfo, out XrEyeTrackerHTC value);
            DEBUG("CreateEyeTracker() " +  " tracker: " + value);
            return result == XrResult.XR_SUCCESS;
        }

        /// <summary>
        /// Releases the eye tracker and the underlying resources when the eye tracking experience is over.
        /// </summary>
        /// <param name="eyeTracker">An XrEyeTrackerHTC previously created by xrCreateEyeTrackerHTC.</param>
        /// <returns>XR_SUCCESS for success.</returns>
        public XrResult DestroyEyeTracker(XrEyeTrackerHTC eyeTracker)
        {
            XrResult result = DestroyEyeTrackerHTC(eyeTracker);
            DEBUG("DestroyEyeTracker() " + eyeTracker + ", result: " + result);

            return result;
        }
        /// <summary>
        /// Releases the eye tracker and the underlying resources when the eye tracking experience is over.
        /// </summary>
        /// <returns>True for success.</returns>
        public bool DestroyEyeTracker()
        {
            if (!hasEyeTracker)
            {
                DEBUG("DestroyEyeTracker() no " + "tracker.");
                return true;
            }

            XrResult ret = XrResult.XR_ERROR_VALIDATION_FAILURE;

            ret = DestroyEyeTracker(m_EyeTracker);
            hasEyeTracker = false;
            m_EyeTracker = 0;

            return ret == XrResult.XR_SUCCESS;
        }

        private XrEyeGazeDataHTC m_gazes = new XrEyeGazeDataHTC();// = new XrEyeGazeDataHTC(XrStructureType.XR_TYPE_EYE_GAZE_DATA_HTC, IntPtr.Zero, 0);
        /// <summary>
        /// Retrieves an array of <see cref="XrSingleEyeGazeDataHTC">XrSingleEyeGazeDataHTC</see> containing the returned eye gaze directions.
        /// </summary>
        /// <param name="out_gazes">Output parameter to retrieve an array of <see cref="XrSingleEyeGazeDataHTC">XrSingleEyeGazeDataHTC</see>.</param>
        /// <returns>True for success.</returns>
        public bool GetEyeGazeData(out XrSingleEyeGazeDataHTC[] out_gazes)
        {
            m_gazes.type = XrStructureType.XR_TYPE_EYE_GAZE_DATA_HTC;
            m_gazes.next = IntPtr.Zero;
            m_gazes.time =  m_frameState.predictedDisplayTime;

            out_gazes = m_gazes.gaze;
            XrEyeGazeDataInfoHTC gazeInfo = new XrEyeGazeDataInfoHTC(
                in_type: XrStructureType.XR_TYPE_EYE_GAZE_DATA_INFO_HTC,
                in_next: IntPtr.Zero,
                in_baseSpace: GetCurrentAppSpace(),
                in_time: m_frameState.predictedDisplayTime);
            if (GetEyeGazeData(m_EyeTracker, gazeInfo, out m_gazes) == XrResult.XR_SUCCESS)
            {
                out_gazes = m_gazes.gaze;
                return true;
            }
            return false;
        }
        /// <summary>
        /// Retrieves the <see cref="XrEyeGazeDataHTC">XrEyeGazeDataHTC</see> data of a <see cref="XrEyeTrackerHTC">XrEyeTrackerHTC</see>.
        /// </summary>
        /// <param name="eyeTracker">An <see cref="XrEyeTrackerHTC">XrEyeTrackerHTC</see> previously created by <see cref="ViveEyeTrackerHelper.xrCreateEyeTrackerHTCDelegate">xrCreateEyeTrackerHTC</see>.</param>
        /// <param name="gazeInfo">The information to get eye gaze.</param>
        /// <param name="eyeGazes">Output parameter to retrieve a pointer to <see cref="XrEyeGazeDataHTC">XrEyeGazeDataHTC</see> receiving the returned eye poses.</param>
        /// <returns>XR_SUCCESS for success.</returns>
        public XrResult GetEyeGazeData(XrEyeTrackerHTC eyeTracker, XrEyeGazeDataInfoHTC gazeInfo, out XrEyeGazeDataHTC eyeGazes)
        {
            m_gazes.type = XrStructureType.XR_TYPE_EYE_GAZE_DATA_HTC;
            m_gazes.next = IntPtr.Zero;
            m_gazes.time = m_frameState.predictedDisplayTime;
            eyeGazes = m_gazes;
            XrResult result = XrResult.XR_ERROR_VALIDATION_FAILURE;
            result = GetEyeGazeDataHTC(eyeTracker,ref gazeInfo,ref m_gazes);
            if (result == XrResult.XR_SUCCESS) { eyeGazes = m_gazes; }
            
            return result;
        }

        private XrEyePupilDataHTC m_eyePupilData = new XrEyePupilDataHTC();

        /// <summary>
        /// Retrieves an array of <see cref="XrSingleEyePupilDataHTC">XrSingleEyePupilDataHTC</see> containing the returned data for user's pupils.
        /// </summary>
        /// <param name="pupilData">Output parameter to retrieve an array of <see cref="XrSingleEyePupilDataHTC">XrSingleEyePupilDataHTC</see>.</param>
        /// <returns>XR_SUCCESS for success.</returns>
        public bool GetEyePupilData(out XrSingleEyePupilDataHTC[] pupilData)
        {
            m_eyePupilData.type = XrStructureType.XR_TYPE_EYE_PUPIL_DATA_HTC;
            m_eyePupilData.next = IntPtr.Zero;
            m_eyePupilData.time = m_frameState.predictedDisplayTime;
            pupilData = m_eyePupilData.pupilData;
            XrEyePupilDataInfoHTC pupilDataInfo = new XrEyePupilDataInfoHTC(
                in_type: XrStructureType.XR_TYPE_EYE_PUPIL_DATA_INFO_HTC,
                in_next: IntPtr.Zero);
            if (GetEyePupilData(m_EyeTracker, pupilDataInfo, out m_eyePupilData) == XrResult.XR_SUCCESS)
            {
                pupilData = m_eyePupilData.pupilData;
                return true;
            }
            return false;
        }

        /// <summary>
        /// Retrieves the <see cref="XrEyePupilDataHTC">XrEyePupilDataHTC</see> data of a <see cref="XrEyeTrackerHTC">XrEyeTrackerHTC</see>.
        /// </summary>
        /// <param name="eyeTracker">An <see cref="XrEyeTrackerHTC">XrEyeTrackerHTC</see> previously created by <see cref="ViveEyeTrackerHelper.xrCreateEyeTrackerHTCDelegate">xrCreateEyeTrackerHTC</see>.</param>
        /// <param name="pupilDataInfo">The information to get pupil data.</param>
        /// <param name="pupilData">A pointer to <see cref="XrEyePupilDataHTC">XrEyePupilDataHTC</see> returned by the runtime.</param>
        /// <returns>XR_SUCCESS for success.</returns>
        public XrResult GetEyePupilData(XrEyeTrackerHTC eyeTracker, XrEyePupilDataInfoHTC pupilDataInfo, out XrEyePupilDataHTC pupilData)
        {
            m_eyePupilData.type = XrStructureType.XR_TYPE_EYE_PUPIL_DATA_HTC;
            m_eyePupilData.next = IntPtr.Zero;
            m_eyePupilData.time = m_frameState.predictedDisplayTime;
            pupilData = m_eyePupilData;
            XrResult result = XrResult.XR_ERROR_VALIDATION_FAILURE;
            result = GetEyePupilDataHTC(eyeTracker,ref pupilDataInfo, ref m_eyePupilData);
            if (result == XrResult.XR_SUCCESS) { pupilData = m_eyePupilData; }
            return result;
        }

        private XrEyeGeometricDataHTC m_eyeGeometricData = new XrEyeGeometricDataHTC();//XrStructureType.XR_TYPE_EYE_GEOMETRIC_DATA_HTC, IntPtr.Zero, 0);
        /// <param name="geometricData">Output parameter to retrieve an array of <see cref="XrSingleEyeGeometricDataHTC">XrSingleEyeGeometricDataHTC</see>.</param>
        /// <returns>XR_SUCCESS for success.</returns>
        public bool GetEyeGeometricData(out XrSingleEyeGeometricDataHTC[] geometricData)
        {
            m_eyeGeometricData.type = XrStructureType.XR_TYPE_EYE_GEOMETRIC_DATA_HTC;
            m_eyeGeometricData.next = IntPtr.Zero;
            m_eyeGeometricData.time = m_frameState.predictedDisplayTime;
            geometricData = m_eyeGeometricData.eyeGeometricData;
            XrEyeGeometricDataInfoHTC eyeGeometricDataInfo = new XrEyeGeometricDataInfoHTC(
                in_type: XrStructureType.XR_TYPE_EYE_GEOMETRIC_DATA_INFO_HTC,
                in_next: IntPtr.Zero);
            if (GetEyeGeometricData(m_EyeTracker, eyeGeometricDataInfo, out m_eyeGeometricData) == XrResult.XR_SUCCESS)
            {
                geometricData = m_eyeGeometricData.eyeGeometricData;
                return true;
            }
            return false;
        }

        /// <param name="eyeTracker">An <see cref="XrEyeTrackerHTC">XrEyeTrackerHTC</see> previously created by <see cref="ViveEyeTrackerHelper.xrCreateEyeTrackerHTCDelegate">xrCreateEyeTrackerHTC</see>.</param>
        /// <param name="eyeGeometricDataInfo">A pointer to <see cref="XrEyeGeometricDataInfoHTC">XrEyeGeometricDataInfoHTC</see> structure.</param>
        /// <param name="eyeGeometricData">A pointer to <see cref="XrEyeGeometricDataHTC">XrEyeGeometricDataHTC</see> returned by the runtime.</param>
        /// <returns>XR_SUCCESS for success.</returns>
        public XrResult GetEyeGeometricData(XrEyeTrackerHTC eyeTracker, XrEyeGeometricDataInfoHTC eyeGeometricDataInfo, out XrEyeGeometricDataHTC eyeGeometricData)
        {
            m_eyeGeometricData.type = XrStructureType.XR_TYPE_EYE_GEOMETRIC_DATA_HTC;
            m_eyeGeometricData.next = IntPtr.Zero;
            m_eyeGeometricData.time = m_frameState.predictedDisplayTime;
            eyeGeometricData = m_eyeGeometricData;
            XrResult result = XrResult.XR_ERROR_VALIDATION_FAILURE;
            result = GetEyeGeometricDataHTC(eyeTracker,ref eyeGeometricDataInfo, ref m_eyeGeometricData);
            if (result == XrResult.XR_SUCCESS) { eyeGeometricData = m_eyeGeometricData; }
            return result;
        }
    }
}
