// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.Runtime.InteropServices;
using TrackableId = UnityEngine.XR.ARSubsystems.TrackableId;

namespace Microsoft.MixedReality.OpenXR
{
    /// <summary>
    /// Provides helper functions to convert an Unity Anchor object to underlying OpenXR anchor handle or SpatialAnchor COM object.
    /// </summary>
    public static class AnchorConverter
    {
        private static MixedRealityFeaturePlugin Feature => OpenXRFeaturePlugin<MixedRealityFeaturePlugin>.Feature;

        /// <summary>
        /// Get the OpenXR handle of the given nativePtr from ARAnchor or XRAnchor object if available, or return 0.
        /// </summary>
        /// <param name="nativePtr">The nativePtr obtained from either XRAnchor.nativePtr or ARAnchor.nativePtr.</param>
        /// <returns>XrAnchorMSFT handle that represents the underlying OpenXR anchor of given nativePtr, or 0 when such associated handle cannot be found.</returns>
        public static ulong ToOpenXRHandle(IntPtr nativePtr)
        {
            if (nativePtr == null)
                return 0;

            NativeAnchorData data = Marshal.PtrToStructure<NativeAnchorData>(nativePtr);
            if (data.version == 1)
            {
                return data.anchorHandle;
            }
            return 0;
        }

        /// <summary>
        /// Create a new ARAnchor from the given OpenXR XRSpatialAnchorMSFT handle.
        /// </summary>
        /// <param name="openxrAnchorHandle">A valid OpenXR XRSpatialAnchorMSFT handle.</param>
        /// <returns>Returns the trackable id representing the Unity anchor or <see cref="TrackableId.invalidId"/> if the conversion was unsuccessful.</returns>
        /// <remarks>The newly created TrackableId will not be added to <see cref="UnityEngine.XR.ARFoundation.ARTrackableManager{TSubsystem, TSubsystemDescriptor, TProvider, TSessionRelativeData, TTrackable}.trackables"/> collection until the next frame's Update.
        /// The app should listen to the <see cref="UnityEngine.XR.ARFoundation.ARAnchorManager.anchorsChanged"/> event for the added ARAnchor object with the returned trackableId.</remarks>
        public static TrackableId CreateFromOpenXRHandle(ulong openxrAnchorHandle)
        {
            if (Feature.IsValidAndEnabled() && openxrAnchorHandle != 0)
            {
                Guid guid = NativeLib.TryCreateARAnchorFromOpenXRHandle(openxrAnchorHandle);
                return FeatureUtils.ToTrackableId(guid);
            }
            return TrackableId.invalidId;
        }

        /// <summary>
        /// Get a COM wrapper object of Windows.Perception.Spatial.SpatialAnchor from the given ARAnchor's nativePtr.
        /// </summary>
        /// <param name="nativePtr">The nativePtr obtained from either XRAnchor.nativePtr or ARAnchor.nativePtr.</param>
        /// <returns>The COM wrapper object of Windows.Perception.Spatial.SpatialAnchor, or null when the conversion failed.</returns>
        public static object ToPerceptionSpatialAnchor(IntPtr nativePtr)
        {
            if (Feature.IsValidAndEnabled() && nativePtr != IntPtr.Zero)
            {
                IntPtr unknown = NativeLib.TryAcquirePerceptionSpatialAnchor(ToOpenXRHandle(nativePtr));
                if (unknown != IntPtr.Zero)
                {
                    object result = Marshal.GetObjectForIUnknown(unknown);
                    Marshal.Release(unknown);   // Balance the ref count because "feature.TryAcquire" increment it on return.
                    return result;
                }
            }
            return null;
        }

        /// <summary>
        /// Get a COM wrapper object of Windows.Perception.Spatial.SpatialAnchor from the given TrackableId.
        /// If failed, the function returns nullptr.
        /// </summary>
        /// <param name="trackableId">An existing XRAnchor or ARAnchor's ID.</param>
        /// <returns>The COM wrapper object of Windows.Perception.Spatial.SpatialAnchor, or null when the conversion failed.</returns>
        public static object ToPerceptionSpatialAnchor(TrackableId trackableId)
        {
            if (Feature.IsValidAndEnabled() && trackableId != TrackableId.invalidId)
            {
                IntPtr unknown = NativeLib.TryAcquirePerceptionSpatialAnchor(FeatureUtils.ToGuid(trackableId));
                if (unknown != IntPtr.Zero)
                {
                    object result = Marshal.GetObjectForIUnknown(unknown);
                    Marshal.Release(unknown);   // Balance the ref count because "feature.TryAcquire" increment it on return.
                    return result;
                }
            }
            return null;
        }

        /// <summary>
        /// Creating a new ARAnchor from the given Windows.Perception.Spatial.SpatialAnchor.
        /// If failed, the function returns TrackableId.invalidId.
        /// Creates an OpenXR anchor from a Windows.Perception.Spatial.SpatialAnchor and reports it to Unity.
        /// </summary>
        /// <param name="spatialAnchor">Must be a Windows.Perception.Spatial.SpatialAnchor.</param>
        /// <returns>Returns the trackable id representing the Unity anchor or <see cref="TrackableId.invalidId"/> if the conversion was unsuccessful.</returns>
        /// <remarks>The newly created TrackableId will not be added to <see cref="UnityEngine.XR.ARFoundation.ARTrackableManager{TSubsystem, TSubsystemDescriptor, TProvider, TSessionRelativeData, TTrackable}.trackables"/> collection until the next frame's Update.
        /// The app should listen to the <see cref="UnityEngine.XR.ARFoundation.ARAnchorManager.anchorsChanged"/> event for the added ARAnchor object with the returned trackableId.</remarks>
        [Obsolete("Obsolete and will be removed in future releases.  Use the `CreateFromPerceptionSpatialAnchor` function instead.")]
        public static TrackableId FromPerceptionSpatialAnchor(object spatialAnchor)
        {
            return CreateFromPerceptionSpatialAnchor(spatialAnchor);
        }

        /// <summary>
        /// Creating a new ARAnchor from the given Windows.Perception.Spatial.SpatialAnchor.
        /// If failed, the function returns TrackableId.invalidId.
        /// Creates an OpenXR anchor from a Windows.Perception.Spatial.SpatialAnchor and reports it to Unity.
        /// </summary>
        /// <param name="spatialAnchor">Must be a Windows.Perception.Spatial.SpatialAnchor.</param>
        /// <returns>Returns the trackable id representing the Unity anchor or <see cref="TrackableId.invalidId"/> if the conversion was unsuccessful.</returns>
        /// <remarks>The newly created TrackableId will not be added to <see cref="UnityEngine.XR.ARFoundation.ARTrackableManager{TSubsystem, TSubsystemDescriptor, TProvider, TSessionRelativeData, TTrackable}.trackables"/> collection until the next frame's Update.
        /// The app should listen to the <see cref="UnityEngine.XR.ARFoundation.ARAnchorManager.anchorsChanged"/> event for the added ARAnchor object with the returned trackableId.</remarks>
        public static TrackableId CreateFromPerceptionSpatialAnchor(object spatialAnchor)
        {
            if (Feature.IsValidAndEnabled() && spatialAnchor != null)
            {
                Guid guid = NativeLib.TryCreateARAnchorFromPerceptionAnchor(spatialAnchor);
                return FeatureUtils.ToTrackableId(guid);
            }
            return TrackableId.invalidId;
        }

        /// <summary>
        /// Replaces the underlying platform anchor for an existing XRAnchor/ARAnchor represented by the
        /// given TrackableId, so the Unity anchor will instead be located by the given SpatialAnchor.
        /// </summary>
        /// <remarks>Use this function instead of <see cref="FromPerceptionSpatialAnchor"/> to avoid creating new ARAnchor on every new platform anchor.</remarks>
        /// <param name="spatialAnchor">Must be a Windows.Perception.Spatial.SpatialAnchor.</param>
        /// <param name="existingId">An id representing an existing XRAnchor/ARAnchor.</param>
        /// <returns>Returns the trackable id representing the Unity anchor or <see cref="TrackableId.invalidId"/> if the conversion was unsuccessful.</returns>
        public static TrackableId ReplaceSpatialAnchor(object spatialAnchor, TrackableId existingId)
        {
            if (Feature.IsValidAndEnabled() && spatialAnchor != null)
            {
                Guid guid = NativeLib.TryAcquireAndReplaceXrSpatialAnchor(spatialAnchor, FeatureUtils.ToGuid(existingId));
                return FeatureUtils.ToTrackableId(guid);
            }
            return TrackableId.invalidId;
        }
    }

    namespace ARSubsystems
    {
        /// <summary>
        /// Provides extension function to convert an XRAnchor object to underlying OpenXR anchor handle.
        /// </summary>
        [System.Obsolete("Obsolete and will be removed in future releases.  Use AnchorConverter.ToOpenXRHandle() function instead.", true)]
        public static class XRAnchorExtensions
        {
            /// <summary>
            /// Get the native OpenXR handle of the given XRAnchor object if available, or return 0.
            /// </summary>
            /// <param name="anchor">A valid <see cref="UnityEngine.XR.ARSubsystems.XRAnchor"/> object.</param>
            /// <returns>XrAnchorMSFT handle that represents the underlying OpenXR anchor, or 0 when such associated handle cannot be found.</returns>
            public static ulong GetOpenXRHandle(this UnityEngine.XR.ARSubsystems.XRAnchor anchor)
            {
                return anchor == null ? 0 : AnchorConverter.ToOpenXRHandle(anchor.nativePtr);
            }
        }
    }

    namespace ARFoundation
    {
        /// <summary>
        /// Provides extension function to convert an ARAnchor object to underlying OpenXR anchor handle.
        /// </summary>
        [System.Obsolete("Obsolete and will be removed in future releases.  Use AnchorConverter.ToOpenXRHandle() function instead.", true)]
        public static class ARAnchorExtensions
        {
            /// <summary>
            /// Get the native OpenXR handle of the given ARAnchor object if available, or return 0.
            /// </summary>
            /// <param name="anchor">A valid <see cref="UnityEngine.XR.ARFoundation.ARAnchor"/> object.</param>
            /// <returns>XrAnchorMSFT handle that represents the underlying OpenXR anchor, or 0 when such associated handle cannot be found.</returns>
            public static ulong GetOpenXRHandle(this UnityEngine.XR.ARFoundation.ARAnchor anchor)
            {
                return anchor == null ? 0 : AnchorConverter.ToOpenXRHandle(anchor.nativePtr);
            }
        }
    }
}