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

using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using UnityEngine.XR.ARSubsystems;

namespace Microsoft.MixedReality.OpenXR
{
    /// <summary>
    /// Provides the ability to build up a batch of anchors and export them to a binary stream for transfer.
    /// Typically on a second device, it then supports importing the transfer stream and loading in the original batch of anchors.
    /// </summary>
    /// <remarks>Use of this class requires an ARAnchorManager in the scene or some other manual management of an XRAnchorSubsystem.</remarks>
    public class XRAnchorTransferBatch
    {
        /// <summary>
        /// Constructor.
        /// </summary>
        public XRAnchorTransferBatch() : this(new AnchorTransferBatch()) { }

        private XRAnchorTransferBatch(AnchorTransferBatch anchorTransferBatch)
        {
            m_anchorTransferBatch = anchorTransferBatch;
        }

        private readonly AnchorTransferBatch m_anchorTransferBatch;

        /// <summary>
        /// Provides a list of all identifiers currently mapped in this AnchorTransferBatch.
        /// </summary>
        public IReadOnlyList<string> AnchorNames => m_anchorTransferBatch.AnchorNames;

        /// <summary>
        /// Tries to convert and add an anchor with the corresponding <see cref="TrackableId"/> to an export list.
        /// </summary>
        /// <remarks>Call <see cref="ExportAsync"/> to get the transferable anchor data.</remarks>
        /// <param name="trackableId">The <see cref="TrackableId"/> of an anchor to be exported.</param>
        /// <param name="name">A string to identify this anchor upon import to another device.</param>
        /// <returns>Whether the anchor was successfully converted into a Perception SpatialAnchor and added to the export list.</returns>
        public bool AddAnchor(TrackableId trackableId, string name) => m_anchorTransferBatch.AddAnchor(trackableId, name);

        /// <summary>
        /// Removes an anchor from the transfer batch. Doesn't remove the existing Unity anchor, if one is present.
        /// </summary>
        /// <remarks>After an anchor is removed from the transfer batch, it will still be valid and locatable in the current session.</remarks>
        /// <param name="name">The name of the anchor to be removed from the transfer batch.</param>
        public void RemoveAnchor(string name) => m_anchorTransferBatch.RemoveAnchor(name);

        /// <summary>
        /// Removes all anchors from the transfer batch. Doesn't remove any existing Unity anchors, if present.
        /// </summary>
        /// <remarks>After the anchors are cleared from the transfer batch, they will still be valid and locatable in the current session.</remarks>
        public void Clear() => m_anchorTransferBatch.Clear();

        /// <summary>
        /// Attempts to load a specified anchor from the transfer batch and reports it to Unity as an XRAnchor/ARAnchor.
        /// </summary>
        /// <remarks>It's then typically recommended to use an ARAnchorManager to access the resulting Unity anchor.</remarks>
        /// <param name="name">The anchor's identifier from the transfer batch.</param>
        /// <returns>The <see cref="TrackableId"/> of the resulting Unity anchor if successfully loaded, or TrackableId.invalidId if the given name is not found.</returns>
        public TrackableId LoadAnchor(string name) => m_anchorTransferBatch.LoadAnchor(name);

        /// <summary>
        /// Attempts to load a specified anchor from the transfer batch and replace the specified Unity anchor's tracking data with the new anchor.
        /// </summary>
        /// <param name="name">The anchor's identifier from the transfer batch.</param>
        /// <param name="trackableId">The existing Unity anchor to update to track this new spatial anchor.</param>
        /// <returns>The <see cref="TrackableId"/> of the resulting Unity anchor (usually the same as the passed-in parameter) if successfully loaded, 
        /// or TrackableId.invalidId if the given name is not found.</returns>
        public TrackableId LoadAndReplaceAnchor(string name, TrackableId trackableId) => m_anchorTransferBatch.LoadAndReplaceAnchor(name, trackableId);

        /// <summary>
        /// Exports any anchors added via <see cref="AddAnchor"/> into a Stream for transfer. Use <see cref="ImportAsync(Stream)"/> for reading this Stream.
        /// </summary>
        /// <param name="anchorTransferBatch">The anchor transfer batch instance to export from. This instance should have had anchors added before attempting export.</param>
        /// <returns>A task which, when completed, will contain the exported array, or null if the export was unsuccessful.</returns>
        public static async Task<Stream> ExportAsync(XRAnchorTransferBatch anchorTransferBatch)
        {
            MemoryStream output = new MemoryStream();
            SerializationCompletionReason reason = await anchorTransferBatch.m_anchorTransferBatch.ExportAsync(output);

            if (reason == SerializationCompletionReason.Succeeded)
            {
                return output;
            }

            return null;
        }

        /// <summary>
        /// Imports the provided Stream into an <see cref="XRAnchorTransferBatch"/>.
        /// </summary>
        /// <param name="inputStream">The streamed data representing the result of a call to <see cref="ExportAsync(XRAnchorTransferBatch)"/>. This stream must be readable.</param>
        /// <returns>A task which, when completed, will contain the resulting XRAnchorTransferBatch, or null if the import was unsuccessful.</returns>
        public static async Task<XRAnchorTransferBatch> ImportAsync(Stream inputStream)
        {
            AnchorTransferBatch anchorTransfer = new AnchorTransferBatch();
            SerializationCompletionReason reason = await anchorTransfer.ImportAsync(inputStream);

            if (reason == SerializationCompletionReason.Succeeded)
            {
                return new XRAnchorTransferBatch(anchorTransfer);
            }

            return null;
        }
    }
}