// Copyright HTC Corporation All Rights Reserved.
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.XR;
using VIVE.OpenXR.Feature;
using VIVE.OpenXR.Toolkits;
using VIVE.OpenXR.Toolkits.Anchor;

namespace VIVE.OpenXR.Samples.Anchor
{
    /// <summary>
    /// This script is used to test AnchorManager.
    /// 
    /// Every frame, it will check if persisted anchors are existed.
    /// If existed, it will create anchors from them. If the anchor is existed, it will dispose it first.
    /// 
    /// Anchors created by AnchorManager.CreateAnchor() didn't have tracking ability.
    /// Only Anchors created by AnchorManager.CreateSpatialAnchorFromPersistedAnchor() have tracking ability.
    /// If Anchors has tracking ability, it will update pose over time.
    /// 
    /// The panchorObj1 and panchorObj2 are used to show the pose of the trackable anchors.
    /// 
    /// </summary>
    public class AnchorTestHandle : MonoBehaviour
    {
        public Transform rig;
        public Transform anchorPoseD;
        public Transform anchorPose1;
        public Transform anchorPose2;
        public Transform obj;
        public GameObject panchorObj1;
        public GameObject panchorObj2;
        public AnchorManager.Anchor anchor1;
        public AnchorManager.Anchor anchor2;
        string anchor1FromPA;
        string anchor2FromPA;
        public TextMeshProUGUI statusResponse;
        public TextMeshProUGUI statusOrigin;
        public TextMeshProUGUI statusAnchor;

        public Button btnCreateAnchor1;
        public Button btnCreateAnchor2;
        public Button btnFollowAnchor1;
        public Button btnFollowAnchor2;
        public Button btnResetObj;
        public Button btnClearSpatialAnchors;

        public Button btnPersistAnchor1;
        public Button btnPersistAnchor2;
        public Button btnUnpersistAnchor1;
        public Button btnUnpersistAnchor2;
        public Button btnClearPersistedAnchors;
        public Button btnAcquirePAC;
        public Button btnReleasePAC;

        public Button btnExportAll;
        public Button btnImportAll;
        public Button btnClearExportedAnchors;

        public Button btnFloor;
        public Button btnDevice;
        public Button btnReloadScene;
        public Button btnQuitApp;

        // For Editor test
        public bool testCreateAnchor1;
        public bool testCreateAnchor2;
        public bool testFollowAnchor1;
        public bool testFollowAnchor2;
        public bool testResetObj;
        public bool testClearSpatialAnchors;

        public bool testCreatePAC;
        public bool testDestroyPAC;
        public bool testPersistAnchor1;
        public bool testPersistAnchor2;
        public bool testUnpersistAnchor1;
        public bool testUnpersistAnchor2;
        public bool testClearPersistedAnchors;

        public bool testExportAll;
        public bool testImportAll;
        public bool testClearExportedAnchors;

        public bool testFloor;
        public bool testDevice;
        public bool testReloadScene;

        public XRInputSubsystem xrInputSubsystem;

        private int lastPACount = -1;
        private int lastFoundExportedFilesCount = -1;
        private FutureTask<(XrResult, IntPtr)> taskPAC = null;

        bool isAnchorSupported;
        bool isPAnchorSupported;
        bool isPACollectionAcquired;

        // 持 means "hold", "persisted".
        const string utf8Word = "持";

        // In Update, check enumerated spatial anchors to update these flags
        bool hasPersistedAnchor1;
        string persistedAnchor1Name;
        bool hasPersistedAnchor2;
        string persistedAnchor2Name;

        // Any anchor file in the app's local storage set flag to true
        bool hasPersistedAnchorFiles;

        bool doesUIInteractionsNeedUpdated = false;
        float lastUpdateTime = 0;

        readonly FutureTaskManager<XrSpace, XrResult> tmPA = new FutureTaskManager<XrSpace, XrResult>();
        readonly FutureTaskManager<string, (XrResult, AnchorManager.Anchor)> tmFPA = new FutureTaskManager<string, (XrResult, AnchorManager.Anchor)>();
        readonly List<Task> tasks = new List<Task>();

        void GetXRInputSubsystem()
        {
            List<XRInputSubsystem> xrSubsystemList = new List<XRInputSubsystem>();
            SubsystemManager.GetSubsystems(xrSubsystemList);
            foreach (var xrSubsystem in xrSubsystemList)
            {
                if (xrSubsystem.running)
                {
                    xrInputSubsystem = xrSubsystem;
                    break;
                }
            }
        }

        private void OnEnable()
        {
            btnCreateAnchor1.onClick.AddListener(OnCreateAnchor1);
            btnCreateAnchor2.onClick.AddListener(OnCreateAnchor2);
            btnFollowAnchor1.onClick.AddListener(OnFollowAnchor1);
            btnFollowAnchor2.onClick.AddListener(OnFollowAnchor2);
            btnResetObj.onClick.AddListener(OnResetObj);
            btnClearSpatialAnchors.onClick.AddListener(OnClearAllAnchors);

            btnAcquirePAC.onClick.AddListener(OnAcquirePersistedAnchorCollection);
            btnReleasePAC.onClick.AddListener(OnReleasePersistedAnchorCollection);
            btnPersistAnchor1.onClick.AddListener(OnPersistAnchor1);
            btnPersistAnchor2.onClick.AddListener(OnPersistAnchor2);
            btnUnpersistAnchor1.onClick.AddListener(OnUnpersistAnchor1);
            btnUnpersistAnchor2.onClick.AddListener(OnUnpersistAnchor2);

            btnClearPersistedAnchors.onClick.AddListener(OnClearPersistedAnchors);
            btnExportAll.onClick.AddListener(OnExportAll);
            btnImportAll.onClick.AddListener(OnImportAll);
            btnClearExportedAnchors.onClick.AddListener(OnClearExportedAnchors);

            btnFloor.onClick.AddListener(OnFloor);
            btnDevice.onClick.AddListener(OnDevice);
            btnReloadScene.onClick.AddListener(OnReloadScene);
            btnQuitApp.onClick.AddListener(Application.Quit);
        }

        private void OnAcquirePersistedAnchorCollection()
        {
            if (AnchorManager.IsPersistedAnchorCollectionAcquired()) return;
            if (taskPAC != null) return;

            taskPAC = AnchorManager.AcquirePersistedAnchorCollection();
            // Acturally, it will auto complete by default.  Just make sure we can continue with AutoCompleteTask
            taskPAC.AutoComplete();
            taskPAC.AutoCompleteTask.ContinueWith((act) => {
                taskPAC = null;
                CheckSupported();
                UINeedUpdate();
            });
        }

        private void OnReleasePersistedAnchorCollection()
        {
            // the taskPAC will be canceled in ReleasePersistedAnchorCollection
            taskPAC = null;

            anchor1FromPA = null;
            anchor2FromPA = null;
            hasPersistedAnchor1 = false;
            hasPersistedAnchor2 = false;
            persistedAnchor1Name = null;
            persistedAnchor2Name = null;
            //hasPersistedAnchorFiles = false;

            AnchorManager.ReleasePersistedAnchorCollection();
            CheckSupported();
            UINeedUpdate();
        }


        private void OnDisable()
        {
            // Remove listeners
            btnCreateAnchor1.onClick.RemoveListener(OnCreateAnchor1);
            btnCreateAnchor2.onClick.RemoveListener(OnCreateAnchor2);
            btnFollowAnchor1.onClick.RemoveListener(OnFollowAnchor1);
            btnFollowAnchor2.onClick.RemoveListener(OnFollowAnchor2);
            btnResetObj.onClick.RemoveListener(OnResetObj);
            btnClearSpatialAnchors.onClick.RemoveListener(OnClearAllAnchors);

            btnAcquirePAC.onClick.RemoveListener(OnAcquirePersistedAnchorCollection);
            btnReleasePAC.onClick.RemoveListener(OnReleasePersistedAnchorCollection);
            btnPersistAnchor1.onClick.RemoveListener(OnPersistAnchor1);
            btnPersistAnchor2.onClick.RemoveListener(OnPersistAnchor2);
            btnUnpersistAnchor1.onClick.RemoveListener(OnUnpersistAnchor1);
            btnUnpersistAnchor2.onClick.RemoveListener(OnUnpersistAnchor2);

            btnClearPersistedAnchors.onClick.RemoveListener(OnClearPersistedAnchors);
            btnExportAll.onClick.RemoveListener(OnExportAll);
            btnImportAll.onClick.RemoveListener(OnImportAll);
            btnClearExportedAnchors.onClick.RemoveListener(OnClearExportedAnchors);
            btnFloor.onClick.RemoveListener(OnFloor);

            btnDevice.onClick.RemoveListener(OnDevice);
            btnReloadScene.onClick.RemoveListener(OnReloadScene);
            btnQuitApp.onClick.RemoveListener(Application.Quit);

            // No need to clear all persistance anchor.  Reload scene can test the persist.

            // Disable Persistance Anchor
            AnchorManager.ReleasePersistedAnchorCollection();

            // Dispose all anchors
            OnClearAllAnchors();

            // Dispose all tasks
            foreach (var task in tasks)
            {
                task.Dispose();
            }
            tasks.Clear();

            // Clear all task managers.  It's readonly, so no need to dispose.
            tmPA.Clear();
            tmFPA.Clear();
        }

        private void OnDestroy()
        {
            // Dispose all anchors
            OnClearAllAnchorsInner(true);

            // Dispose all tasks
            foreach (var task in tasks)
            {
                task.Dispose();
            }
            tasks.Clear();

            // Clear all task managers.  It's readonly, so no need to dispose.
            tmPA.Clear();
            tmFPA.Clear();
        }

        void UINeedUpdate()
        {
            doesUIInteractionsNeedUpdated = true;
        }

        void UpdateUIInteractions()
        {
            if ((Time.time - lastUpdateTime) > 2.0f)
            {
                lastUpdateTime = Time.time;
                doesUIInteractionsNeedUpdated = true;
            }

            if (!doesUIInteractionsNeedUpdated)
                return;
            doesUIInteractionsNeedUpdated = false;

            bool hasAnchor1 = anchor1 != null;
            bool hasAnchor2 = anchor2 != null;
            bool hasAnchor = hasAnchor1 || hasAnchor2;

            // Create / Follow / Reset / Dispose
            // If created, can created again.
            // If no Anchor, cannot follow.
            // however these interactions no need block
            btnCreateAnchor1.interactable = isAnchorSupported && !hasPersistedAnchor1;
            btnCreateAnchor2.interactable = isAnchorSupported && !hasPersistedAnchor2;
            btnFollowAnchor1.interactable = hasAnchor1 && !anchor1.IsPersisted;
            btnFollowAnchor2.interactable = hasAnchor2 && !anchor2.IsPersisted;
            btnResetObj.interactable = true;
            btnClearSpatialAnchors.interactable = hasAnchor;

            // Create Persisted Anchor Collection / Destroy Persisted Anchor Collection
            // If has created, can destroy
            // If has not created, can create
            btnAcquirePAC.interactable = isPAnchorSupported && !isPACollectionAcquired && taskPAC == null;
            btnReleasePAC.interactable = isPAnchorSupported && isPACollectionAcquired;

            // Persist / Unpersist / Clear
            // If has anchor, can persist
            // If has persisted anchor, can unpersist
            // If has persisted anchor, can clear
            btnPersistAnchor1.interactable = isPACollectionAcquired && hasAnchor1;
            btnPersistAnchor2.interactable = isPACollectionAcquired && hasAnchor2;
            btnUnpersistAnchor1.interactable = isPACollectionAcquired && hasPersistedAnchor1;
            btnUnpersistAnchor2.interactable = isPACollectionAcquired && hasPersistedAnchor2;
            btnClearPersistedAnchors.interactable = isPACollectionAcquired && (hasPersistedAnchor1 || hasPersistedAnchor2);

            // Export / Import / Clear
            // If has persisted anchor, can export
            // If has exported anchor, can import
            // If has exported anchor, can clear
            btnExportAll.interactable = isPACollectionAcquired && (hasPersistedAnchor1 || hasPersistedAnchor2) && tasks.Count == 0;
            btnImportAll.interactable = isPACollectionAcquired && hasPersistedAnchorFiles && tasks.Count == 0;
            // Not to clear when exporting or importing
            btnClearExportedAnchors.interactable = hasPersistedAnchorFiles && tasks.Count == 0;

            string sa1 = "", sa2 = "";
            if (hasAnchor1)
                sa1 = "A1: " + anchor1.Name + (anchor1.IsTrackable ? " T" : "") + (anchor1.IsPersisted ? " P" : "") + "\n";
            if (hasAnchor2)
                sa2 = "A2: " + anchor2.Name + (anchor2.IsTrackable ? " T" : "") + (anchor2.IsPersisted ? " P" : "") + "\n";
            statusAnchor.text = sa1 + sa2 +
                (hasPersistedAnchor1 ? "PA1: " + persistedAnchor1Name + "\n" : "") +
                (hasPersistedAnchor2 ? "PA2: " + persistedAnchor2Name + "\n" : "");
        }

        void CheckSupported()
        {
            isAnchorSupported = AnchorManager.IsSupported();
            Debug.Log("AnchorTestHandle: Is Anchor supported: " + isAnchorSupported);
            isPAnchorSupported = AnchorManager.IsPersistedAnchorSupported();
            Debug.Log("AnchorTestHandle: Is Persisted Anchor supported: " + isPAnchorSupported);
            isPACollectionAcquired = AnchorManager.IsPersistedAnchorCollectionAcquired();
            Debug.Log("AnchorTestHandle: Is Persisted Anchor Collection acquired: " + isPACollectionAcquired);
        }

        IEnumerator Start()
        {
            CheckSupported();
            UINeedUpdate();
            UpdateUIInteractions();

            yield return null;  // yield and let Time.unscaledTime to be updated
            float t = Time.unscaledTime;
            while (xrInputSubsystem == null)
            {
                yield return null;
                GetXRInputSubsystem();
                if (Time.unscaledTime - t > 5)
                {
#if UNITY_EDITOR
                    Debug.LogError("Get XRInputSubsystem timeout.  Check if you acturally enable the OpenXR's Mock Runtime.");
#else
                    Debug.LogError("Get XRInputSubsystem timeout.  Check if you acturally enable any VR runtime."); 
#endif
                    statusResponse.text = "No VR runtime";
                    yield break;
                }
            }

            // Check again
            CheckSupported();
            UINeedUpdate();

            if (!isPAnchorSupported) yield break;

            Debug.Log("AnchorTestHandle: AcquirePersistedAnchorCollection");

            taskPAC = AnchorManager.AcquirePersistedAnchorCollection();
            taskPAC.Debug = true;
            while (!AnchorManager.IsPersistedAnchorCollectionAcquired())
            {
                Debug.Log("AnchorTestHandle: Wait PersistedAnchorCollection acquired");
                yield return null;
            }
            taskPAC = null;

            AnchorManager.GetPersistedAnchorProperties(out ViveAnchor.XrPersistedAnchorPropertiesGetInfoHTC properties);
            Debug.Log("AnchorTestHandle: PersistAnchorProperties.maxPersistedAnchorCount=" + properties.maxPersistedAnchorCount);

            CheckSupported();

            Debug.Log("AnchorTestHandle: Start() finished");

            UINeedUpdate();
            UpdateUIInteractions();
        }

        public Pose GetRelatedPoseToRig(Transform t)
        {
            return new Pose(rig.InverseTransformPoint(t.position), Quaternion.Inverse(rig.rotation) * t.rotation);
        }

        string MakeAnchorName(string userName)
        {
            // userName means user defined name.  Add frame count to make it unique.
            return userName + " (" + Time.frameCount + ")";
        }

        string MakePersistedAnchorName(string anchorName)
        {
            // the anchor name support UTF-8 encoding.
            return anchorName + utf8Word;
        }

        string MakeSpatialAnchorName(string persistedName)
        {
            // Remove the utf8Word from the anchor name
            return persistedName.Substring(0, persistedName.Length - utf8Word.Length);
        }

        /// <summary>
        /// Help create anchor by anchor manager
        /// </summary>
        /// <param name="relatedPose">pose related to camera rig</param>
        /// <param name="name">the anchor's name</param>
        /// <returns></returns>
        AnchorManager.Anchor CreateAnchor(Pose relatedPose, string name)
        {
            if (!AnchorManager.IsSupported())
            {
                Debug.LogError("AnchorManager: Anchor is not supported.");
                statusResponse.text = "Anchor is not supported.";
                return null;
            }
            var anchor = AnchorManager.CreateAnchor(relatedPose, MakeAnchorName(name));
            if (anchor == null)
            {
                statusResponse.text = "Create " + name + " failed";
                Debug.LogError("Create " + name + " failed");
                // Even error, still got.  Use fake data.
                return anchor;
            }
            else
            {
                string msg = "Create Anchor n=" + anchor.Name + " space=" + anchor.GetXrSpace() + " at p=" + relatedPose.position + " & r=" + relatedPose.rotation.eulerAngles;
                statusResponse.text = msg;
                Debug.Log(msg);
                return  anchor;
            }
        }

        public void OnCreateAnchor1()
        {
            if (anchor1 != null)
            {
                anchor1.Dispose();
                anchor1 = null;
            }
            anchor1 = CreateAnchor(GetRelatedPoseToRig(anchorPose1), "anchor1");
            UINeedUpdate();
        }

        public void OnCreateAnchor2()
        {
            if (anchor2 != null)
            {
                anchor2.Dispose();
                anchor2 = null;
            }
            anchor2 = CreateAnchor(GetRelatedPoseToRig(anchorPose2), "anchor2");
            UINeedUpdate();
        }

        public void MoveObjToAnchor(AnchorManager.Anchor anchor)
        {
            if (!AnchorManager.IsSupported())
                return;

            if (anchor == null)
            {
                statusResponse.text = "anchor is null";
                return;
            }

            if (AnchorManager.GetTrackingSpacePose(anchor, out Pose pose))
            {
                // Convert tracking space pose to world space pose
                obj.position = rig.TransformPoint(pose.position);
                obj.rotation = rig.rotation * pose.rotation;

                statusResponse.text = "Obj move to " + anchor.GetSpatialAnchorName();
            }
            else
            {
                statusResponse.text = "Fail to get anchor's pose";
            }
        }

        public void OnFollowAnchor1()
        {
            Debug.Log("AnchorTestHandle: OnFollowAnchor1()");
            MoveObjToAnchor(anchor1);
        }

        public void OnFollowAnchor2()
        {
            Debug.Log("AnchorTestHandle: OnFollowAnchor2()");
            MoveObjToAnchor(anchor2);
        }

        public void OnResetObj()
        {
            Debug.Log("AnchorTestHandle: OnResetObj()");
            if (obj != null && anchorPoseD != null)
            {
                obj.position = anchorPoseD.position;
                obj.rotation = anchorPoseD.rotation;
            }

            if (statusResponse != null)
                statusResponse.text = "Obj move to default pose";
        }

        private void OnClearAllAnchorsInner(bool isDestroy = false)
        {
            Debug.Log("AnchorTestHandle: OnClearAllAnchors()");
            try
            {
                if (!isDestroy)
                {
                    // Not to touch object when destroy
                    if (obj != null && anchorPoseD != null)
                    {
                        obj.position = anchorPoseD.position;
                        obj.rotation = anchorPoseD.rotation;
                    }

                    if (statusResponse != null)
                    {
                        if (hasPersistedAnchor1 || hasPersistedAnchor2)
                            statusResponse.text = "Dispose spatial anchors but will create again by persisted";
                        else
                            statusResponse.text = "Dispose spatial anchors";
                    }
                }
            }
            catch (System.Exception e)
            {
                Debug.LogError("AnchorTestHandle: OnClearAllAnchorsInner: " + e.Message);
            }

            if (anchor1 != null)
            {
                anchor1.Dispose();
                anchor1 = null;
            }
            if (anchor2 != null)
            {
                anchor2.Dispose();
                anchor2 = null;
            }
        }

        public void OnClearAllAnchors()
        {
            OnClearAllAnchorsInner();
        }

        public void OnPersistAnchor1()
        {
            Debug.Log("AnchorTestHandle: OnPersistAnchor1()");
            if (anchor1 == null)
            {
                statusResponse.text = "anchor1 is null";
                return;
            }

            // Create new persisted anchor name by frame count.  Not to follow the original anchor name.
            var newName = MakePersistedAnchorName(MakeAnchorName("anchor1"));
            Debug.Log($"AnchorTestHandle: Persist {anchor1.Name} to {newName}");
            var task = tmPA.GetTask(anchor1.GetXrSpace());
            if (task != null)
            {
                statusResponse.text = "Persist " + newName + " is running, please wait";
                return;
            }

            task = AnchorManager.PersistAnchor(anchor1, newName);
            tmPA.AddTask(anchor1.GetXrSpace(), task);
            statusResponse.text = "Persist " + newName + " task is running, please wait";
        }

        public void OnPersistAnchor2()
        {
            Debug.Log("AnchorTestHandle: OnPersistAnchor2()");
            if (anchor2 == null)
            {
                statusResponse.text = "anchor2 is null";
                return;
            }

            // Create new persisted anchor name by frame count.  Not to follow the original anchor name.
            var newName = MakePersistedAnchorName(MakeAnchorName("anchor2"));
            Debug.Log($"AnchorTestHandle: Persist {anchor2.Name} to {newName}");
            var task = tmPA.GetTask(anchor2.GetXrSpace());
            if (task != null)
            {
                statusResponse.text = "Persist " + newName + " is running, please wait";
                return;
            }

            task = AnchorManager.PersistAnchor(anchor2, newName);
            tmPA.AddTask(anchor2.GetXrSpace(), task);
            statusResponse.text = "Persist " + newName + " task is running, please wait";
        }

        public void OnUnpersistAnchor1()
        {
            Debug.Log("AnchorTestHandle: OnUnpersistAnchor1()");
            if (!hasPersistedAnchor1 || string.IsNullOrEmpty(persistedAnchor1Name))
            {
                statusResponse.text = "persist anchor1 not exist";
                return;
            }

            var ret = AnchorManager.UnpersistAnchor(persistedAnchor1Name) == XrResult.XR_SUCCESS;
            if (ret)
                statusResponse.text = "Unpersist " + persistedAnchor1Name + " success";
            else
                statusResponse.text = "Unpersist " + persistedAnchor1Name + " failed: " + ret;

            hasPersistedAnchor1 = false;
            persistedAnchor1Name = null;
            UINeedUpdate();
        }

        public void OnUnpersistAnchor2()
        {
            Debug.Log("AnchorTestHandle: OnUnpersistAnchor2()");
            if (!hasPersistedAnchor2 || string.IsNullOrEmpty(persistedAnchor2Name))
            {
                statusResponse.text = "persist anchor2 not exist";
                return;
            }

            var ret = AnchorManager.UnpersistAnchor(persistedAnchor2Name) == XrResult.XR_SUCCESS;
            if (ret)
                statusResponse.text = "Unpersist " + persistedAnchor2Name + " success";
            else
                statusResponse.text = "Unpersist " + persistedAnchor2Name + " failed: " + ret;

            hasPersistedAnchor2 = false;
            persistedAnchor2Name = null;
            UINeedUpdate();
        }

        public void OnClearPersistedAnchors()
        {
            Debug.Log("AnchorTestHandle: OnClearPersistedAnchors()");
            var ret = AnchorManager.ClearPersistedAnchors() == XrResult.XR_SUCCESS;
            if (ret)
            {
                hasPersistedAnchor1 = false;
                hasPersistedAnchor2 = false;
                persistedAnchor1Name = null;
                persistedAnchor2Name = null;

                statusResponse.text = "Clear persisted anchors success";
            }
            else
                statusResponse.text = "Clear persisted anchors failed: " + ret;
        }

        public IEnumerator WaitExportFinish()
        {
            while (tasks.Count > 0)
            {
                Debug.Log("AnchorTestHandle: WaitExportFinish: " + tasks.Count);
                foreach (var task in tasks)
                {
                    if (task.IsCompleted)
                    {
                        Task<(XrResult, string, byte[])> t = task as Task<(XrResult, string, byte[])>;
                        if (t == null)
                        {
                            tasks.Remove(task);
                            break;
                        }

                        if (t.Result.Item1 == XrResult.XR_SUCCESS)
                        {
                            // write to file
                            string path = GetExportPath();
                            if (!Directory.Exists(path))
                            {
                                Directory.CreateDirectory(path);
                            }

                            string paname = t.Result.Item2;
                            if (!paname.EndsWith(utf8Word))
                                throw new System.Exception("Anchor name is not ended with " + utf8Word);
                            paname = paname.Substring(0, paname.Length - 1);
                            string file = Path.Combine(path, paname + ".pa");
                            Debug.Log("AnchorTestHandle: Data length: " + t.Result.Item3.Length);
                            File.WriteAllBytes(file, t.Result.Item3);
                            Debug.Log("AnchorTestHandle: Exported anchor to " + file);
                            statusResponse.text = "Exported anchor to " + file;
                        }
                        else
                        {
                            Debug.LogError("AnchorTestHandle: Export persisted anchor failed: " + t.Result.Item1);
                            statusResponse.text = "Export persisted anchor failed: " + t.Result.Item1;
                        }
                        tasks.Remove(task);
                        break;
                    }
                }
                yield return null;
            }

            UINeedUpdate();

            statusResponse.text = "All persisted anchors exported";
        }

        public void OnExportAll()
        {
            Debug.Log("AnchorTestHandle: OnExportAll()");
            // Create export folder
            string path = GetExportPath();
            if (!Directory.Exists(path))
                Directory.CreateDirectory(path);

            OnClearExportedAnchors();

            if (tasks.Count > 0)
            {
                statusResponse.text = "Other task is running, please wait";
                return;
            }

            statusResponse.text = "Exporting anchors";

            if (hasPersistedAnchor1)
            {
                Task t = AnchorManager.ExportPersistedAnchor(persistedAnchor1Name);
                tasks.Add(t);
            }

            if (hasPersistedAnchor2)
            {
                Task t = AnchorManager.ExportPersistedAnchor(persistedAnchor2Name);
                tasks.Add(t);
            }

            // Has tasks, export/import/clear buttons should be disabled
            UINeedUpdate();

            StartCoroutine(WaitExportFinish());
        }

        public IEnumerator WaitImportFinish()
        {
            while (tasks.Count > 0)
            {
                foreach (var task in tasks)
                {
                    if (task.IsCompleted)
                    {
                        tasks.Remove(task);
                        break;
                    }
                }
                yield return null;
            }

            // No tasks, export/import/clear buttons should be enabled
            UINeedUpdate();
            statusResponse.text = "All persisted anchors imported";
        }

        public void OnImportAll()
        {
            Debug.Log("AnchorTestHandle: OnImportAll()");
            if (tasks.Count > 0)
            {
                statusResponse.text = "Other task is running, please wait";
                return;
            }

            var list = GetExportedFilesList();
            if (list == null)
            {
                statusResponse.text = "No exported anchor files";
                return;
            }

            string names = "";
            try
            {
                foreach (var file in list)
                {
                    Task tread = new Task(async () =>
                    {
                        try
                        {
                            Debug.Log("AnchorTestHandle: Read file: " + file);
                            var data = File.ReadAllBytes(file);
                            Debug.Log("AnchorTestHandle: data length: " + data.Length);

                            AnchorManager.GetPersistedAnchorNameFromBuffer(data, out string name);
                            Debug.Log("AnchorTestHandle: data is from " + name);
                            names += name + "\n";

                            Task<XrResult> importTask = AnchorManager.ImportPersistedAnchor(data);
                            await importTask;

                            if (importTask.Result == XrResult.XR_SUCCESS)
                                Debug.Log("AnchorTestHandle: Import persisted anchor success");
                            else
                                Debug.LogError("AnchorTestHandle: Import persisted anchor failed: " + importTask.Result);
                        }
                        catch (System.Exception e)
                        {
                            Debug.LogError("AnchorTestHandle: Read file " + file + " failed: " + e.Message);
                            statusResponse.text = "Read file " + file + " failed: " + e.Message;
                        }
                    });
                    tread.Start();
                    tasks.Add(tread);
                }
            }
            catch (System.Exception e)
            {
                Debug.LogError("AnchorTestHandle: Import persisted anchor failed: " + e.Message);
                statusResponse.text = "Import persisted anchor failed: " + e.Message;
            }

            // Has tasks, export/import/clear buttons should be disabled
            UINeedUpdate();
            statusResponse.text = "Importing " + list.Count + "persist anchor(s):\n" + names;


            StartCoroutine(WaitImportFinish());
        }

        public void OnClearExportedAnchors()
        {
            Debug.Log("AnchorTestHandle: OnClearExportedAnchors()");
            var list = GetExportedFilesList();
            if (list != null)
            {
                foreach (var file in list)
                {
                    Debug.Log("AnchorTestHandle: Delete file " + file);
                    File.Delete(file);
                }
                hasPersistedAnchorFiles = false;
                statusResponse.text = "Del " + list.Count + " files";
            }
            UINeedUpdate();
        }

        string GetExportPath()
        {
            return Path.Combine(Application.persistentDataPath, "Anchors");
        }

        List<string> GetExportedFilesList()
        {
            // Get persistance storage path
            // Check if any anchor file in the app's local storage
            string path = GetExportPath();
            if (!Directory.Exists(path)) return null;

            string[] files = Directory.GetFiles(path);
            List<string> anchorFiles = new List<string>();
            if (files.Length == 0) return null;
            foreach (string file in files ) {
                if (file.EndsWith(".pa"))
                    anchorFiles.Add(file);
            }
            // Print log if files count changed
            if (anchorFiles.Count != lastFoundExportedFilesCount)
            {
                Debug.Log("AnchorTestHandle: Found " + anchorFiles.Count + " anchor files");
                foreach (var file in anchorFiles)
                    Debug.Log("AnchorTestHandle:     " + file);
                lastFoundExportedFilesCount = anchorFiles.Count;
            }

            hasPersistedAnchorFiles = anchorFiles.Count > 0;
            return anchorFiles;
        }

        public void CheckExportedFiles()
        {
            if (hasPersistedAnchorFiles) return;

            GetExportedFilesList();
        }

        public void EnumeratePersistedAnchors()
        {
            if (!isAnchorSupported || !isPAnchorSupported || !isPACollectionAcquired)
                return;

            if (AnchorManager.GetNumberOfPersistedAnchors(out int count) != XrResult.XR_SUCCESS) {
                Debug.LogError("AnchorTestHandle: GetNumberOfPersistedAnchors failed");
                return;
            }

            var tmpHasPA1 = hasPersistedAnchor1;
            var tmpHasPA2 = hasPersistedAnchor2;
            hasPersistedAnchor1 = false;
            hasPersistedAnchor2 = false;
            persistedAnchor1Name = null;
            persistedAnchor2Name = null;

            if (count != lastPACount)
            {
                Debug.Log("AnchorTestHandle: GetNumberOfPersistedAnchors=" + count);
                lastPACount = count;
            }

            if (count == 0)
                return;

            // Enumerate persisted anchors
            if (AnchorManager.EnumeratePersistedAnchorNames(out string[] names) != XrResult.XR_SUCCESS)
            {
                Debug.LogError("AnchorTestHandle: EnumeratePersistedAnchorNames failed");
                return;
            }

            foreach (var name in names)
            {
                if (name.EndsWith(utf8Word))
                {
                    if (name.StartsWith("anchor1"))
                    {
                        if (!tmpHasPA1)
                            Debug.Log("AnchorTestHandle: Found persisted anchor1: " + name);
                        hasPersistedAnchor1 = true;
                        persistedAnchor1Name = name;
                    }
                    else if (name.StartsWith("anchor2"))
                    {
                        if (!tmpHasPA2)
                            Debug.Log("AnchorTestHandle: Found persisted anchor2: " + name);
                        hasPersistedAnchor2 = true;
                        persistedAnchor2Name = name;
                    }
                }

                // Others don't care
            }

            if (hasPersistedAnchor1 != tmpHasPA1 && hasPersistedAnchor1 == false)
                Debug.Log("AnchorTestHandle: Lost persisted anchor1");

            if (hasPersistedAnchor2 != tmpHasPA2 && hasPersistedAnchor2 == false)
                Debug.Log("AnchorTestHandle: Lost persisted anchor2");

            if (hasPersistedAnchor1 != tmpHasPA1 || hasPersistedAnchor2 != tmpHasPA2)
                UINeedUpdate();
        }

        /// <summary>
        /// If both anchor and persited anchor are existed, check if the anchor is created from the persisted anchor.  If not, destroy the anchor.
        /// </summary>
        /// <param name="hasPA">Persisted anchor exist</param>
        /// <param name="paName">Persisted anchor's name</param>
        /// <param name="anchor">if anchor is not null, anchor exist</param>
        /// <param name="anchorFromPA">If the anchor is from a persisted anchor, the persisted anchor's name</param>
        /// <returns>If the anchor need be destroyed</returns>
        bool NeedDestroy(bool hasPA, string paName, AnchorManager.Anchor anchor, string anchorFromPA) {
            // If no persisted anchor, no need to destroy the anchor.
            if (!hasPA)
                return false;
            // If no anchor, no need to destroy the anchor.
            if (anchor == null)
                return false;
            // If the anchor is created from the same persisted anchor, no need to destroy it.
            if (anchorFromPA == paName)
                return false;
            // If the anchor is created from other persisted anchor, need to destroy it.
            return true;
        }

        bool NeedCancelCFPA(bool hasPA, string paName, FutureTask<(XrResult, AnchorManager.Anchor)> task, string anchorFromPA) {
            // If task is already created from the same persisted anchor, no need to cancel it.
            if (anchorFromPA == paName)
                return false;
            // If task is already created from the same persisted anchor, no need to cancel it.
            // If no persisted anchor, keep the task.
            return hasPA && task != null;
        }

        // If persisted anchors are existed, create anchor from them.
        void UpdateAnchorsIfPersistExist()
        {
            // If both anchor and persited anchor are existed, check if the anchor is created from the persisted anchor.  If not, dispose the anchor.

            // Check if the anchor is realted to the persisted anchor.
            bool a1NeedDispose = NeedDestroy(hasPersistedAnchor1, persistedAnchor1Name, anchor1, anchor1FromPA);
            bool a2NeedDispose = NeedDestroy(hasPersistedAnchor2, persistedAnchor2Name, anchor2, anchor2FromPA);

            if (a1NeedDispose)
            {
                Debug.Log("AnchorTestHandle: Dispose existed anchor1 because we want to create it from persisted anchor");
                anchor1.Dispose();
                anchor1 = null;
                anchor1FromPA = "";
            }

            if (a2NeedDispose)
            {
                Debug.Log("AnchorTestHandle: Dispose existed anchor2 because we want to create it from persisted anchor");
                anchor2.Dispose();
                anchor2 = null;
                anchor2FromPA = "";
            }

            // Use TaskManager to keep the tasks
            var task1 = tmFPA.GetTask(anchor1FromPA);
            var task2 = tmFPA.GetTask(anchor2FromPA);

            // Check if the task is related to the persisted anchor.
            bool cfpa1NeedCancel = NeedCancelCFPA(hasPersistedAnchor1, persistedAnchor1Name, task1, anchor1FromPA);
            bool cfpa2NeedCancel = NeedCancelCFPA(hasPersistedAnchor2, persistedAnchor2Name, task2, anchor2FromPA);

            if (cfpa1NeedCancel)
            {
                tmFPA.RemoveTask(task1);
                task1 = null;
                anchor1FromPA = "";
            }

            if (cfpa2NeedCancel)
            {
                tmFPA.RemoveTask(task2);
                task2 = null;
                anchor2FromPA = "";
            }

            bool needCreateAnchor1 = hasPersistedAnchor1 && anchor1 == null && task1 == null && !string.IsNullOrEmpty(persistedAnchor1Name);
            if (needCreateAnchor1)
            {
                task1 = AnchorManager.CreateSpatialAnchorFromPersistedAnchor(persistedAnchor1Name, MakeSpatialAnchorName(persistedAnchor1Name));
                tmFPA.AddTask(persistedAnchor1Name, task1);
                anchor1FromPA = persistedAnchor1Name;
            }

            bool needCreateAnchor2 = hasPersistedAnchor2 && anchor2 == null && task2 == null && !string.IsNullOrEmpty(persistedAnchor2Name);
            if (needCreateAnchor2)
            {
                task2 = AnchorManager.CreateSpatialAnchorFromPersistedAnchor(persistedAnchor2Name, MakeSpatialAnchorName(persistedAnchor2Name));
                tmFPA.AddTask(persistedAnchor2Name, task2);
                anchor2FromPA = persistedAnchor2Name;
            }

            if (task1 != null && task1.IsPollCompleted)
            {
                var result = task1.Complete();
                tmFPA.RemoveTask(task1);
                if (result.Item1 != XrResult.XR_SUCCESS)
                {
                    Debug.LogError("AnchorTestHandle: Create anchor1 from persisted anchor failed");
                    statusResponse.text = "Create anchor1 from persisted anchor failed";
                    anchor1FromPA = null;
                }
                else
                {
                    anchor1 = result.Item2;
                }
            }

            if (task2 != null && task2.IsPollCompleted)
            {
                var result = task2.Complete();
                tmFPA.RemoveTask(task2);
                if (result.Item1 != XrResult.XR_SUCCESS)
                {
                    Debug.LogError("AnchorTestHandle: Create anchor2 from persisted anchor failed");
                    statusResponse.text = "Create anchor2 from persisted anchor failed";
                    anchor2FromPA = null;
                }
                else
                {
                    anchor2 = result.Item2;
                }
            }
        }

        float lastUpdateTimeTAP = -1;

        void UpdateTrackableAnchorsPose()
        {
            bool panchorObj1ShouldBeSeen = false;
            bool panchorObj2ShouldBeSeen = false;

            if (anchor1 == null || !anchor1.IsTrackable)
                panchorObj1ShouldBeSeen = false;

            if (anchor2 == null || !anchor2.IsTrackable)
                panchorObj2ShouldBeSeen = false;

            if (anchor1 != null && anchor1.IsTrackable)
            {
                if (AnchorManager.GetTrackingSpacePose(anchor1, out Pose pose))
                {
                    panchorObj1ShouldBeSeen = true;
                    panchorObj1.transform.position = rig.TransformPoint(pose.position);
                    panchorObj1.transform.rotation = rig.rotation * pose.rotation;
                }
                else
                {
                    panchorObj1ShouldBeSeen = false;
                    Debug.LogError("AnchorTestHandle: GetTrackingSpacePose for anchor1 is failed");
                }
            }

            if (anchor2 != null && anchor2.IsTrackable)
            {
                if (AnchorManager.GetTrackingSpacePose(anchor2, out Pose pose))
                {
                    panchorObj2ShouldBeSeen = true;
                    panchorObj2.transform.position = rig.TransformPoint(pose.position);
                    panchorObj2.transform.rotation = rig.rotation * pose.rotation;
                }
                else
                {
                    panchorObj2ShouldBeSeen = false;
                    Debug.LogError("AnchorTestHandle: GetTrackingSpacePose for anchor2 is failed");
                }
            }

            var switchObj1 = panchorObj1ShouldBeSeen != panchorObj1.activeInHierarchy;
            if (switchObj1)
                panchorObj1.SetActive(panchorObj1ShouldBeSeen);

            var switchObj2 = panchorObj2ShouldBeSeen != panchorObj2.activeInHierarchy;
            if (switchObj2)
                panchorObj2.SetActive(panchorObj2ShouldBeSeen);

            // force update log once if switchObj1 or switchObj2
            if (switchObj1 || switchObj2)
                lastUpdateTimeTAP = -1;

            if (panchorObj1ShouldBeSeen || panchorObj2ShouldBeSeen)
            {
                if (Time.time - lastUpdateTimeTAP > 0.25f)
                {
                    StackTraceLogType stackTraceLogTypeOrigin = Application.GetStackTraceLogType(LogType.Log);
                    Application.SetStackTraceLogType(LogType.Log, StackTraceLogType.None);
                    if (panchorObj1ShouldBeSeen)
                    {
                        var pos = panchorObj1.transform.localPosition;
                        var rot = panchorObj1.transform.localRotation.eulerAngles;
                        Debug.Log("AnchorTestHandle: panchorObj1 pos=" + pos.x + "," + pos.y + "," + pos.z);
                        Debug.Log("AnchorTestHandle: panchorObj1 rot=" + rot.x + "," + rot.y + "," + rot.z);
                    }

                    if (panchorObj2ShouldBeSeen)
                    {
                        var pos = panchorObj2.transform.localPosition;
                        var rot = panchorObj2.transform.localRotation.eulerAngles;
                        Debug.Log("AnchorTestHandle: panchorObj2 pos=" + pos.x + "," + pos.y + "," + pos.z);
                        Debug.Log("AnchorTestHandle: panchorObj2 rot=" + rot.x + "," + rot.y + "," + rot.z);
                    }
                    Application.SetStackTraceLogType(LogType.Log, stackTraceLogTypeOrigin);
                    lastUpdateTimeTAP = Time.time;
                }
            }
        }

        void PerformEditorTest()
        {
            if (testCreateAnchor1)
            {
                testCreateAnchor1 = false;
                OnCreateAnchor1();
            }

            if (testCreateAnchor2)
            {
                testCreateAnchor2 = false;
                OnCreateAnchor2();
            }

            if (testFollowAnchor1)
            {
                testFollowAnchor1 = false;
                OnFollowAnchor1();
            }

            if (testFollowAnchor2)
            {
                testFollowAnchor2 = false;
                OnFollowAnchor2();
            }

            if (testResetObj)
            {
                testResetObj = false;
                OnResetObj();
            }

            if (testClearSpatialAnchors)
            {
                testClearSpatialAnchors = false;
                OnClearAllAnchors();
            }

            if (testCreatePAC)
            {
                testCreatePAC = false;
                OnAcquirePersistedAnchorCollection();
            }

            if (testDestroyPAC)
            {
                testDestroyPAC = false;
                OnReleasePersistedAnchorCollection();
            }

            if (testPersistAnchor1)
            {
                testPersistAnchor1 = false;
                OnPersistAnchor1();
            }

            if (testPersistAnchor2)
            {
                testPersistAnchor2 = false;
                OnPersistAnchor2();
            }

            if (testExportAll)
            {
                testExportAll = false;
                OnExportAll();
            }

            if (testImportAll)
            {
                testImportAll = false;
                OnImportAll();
            }

            if (testClearExportedAnchors)
            {
                testClearExportedAnchors = false;
                OnClearExportedAnchors();
            }

            if (testUnpersistAnchor1)
            {
                testUnpersistAnchor1 = false;
                OnUnpersistAnchor1();
            }

            if (testUnpersistAnchor2)
            {
                testUnpersistAnchor2 = false;
                OnUnpersistAnchor2();
            }

            if (testClearPersistedAnchors)
            {
                testClearPersistedAnchors = false;
                OnClearPersistedAnchors();
            }

            if (testFloor)
            {
                testFloor = false;
                OnFloor();
            }

            if (testDevice)
            {
                testDevice = false;
                OnDevice();
            }

            if (testReloadScene)
            {
                testReloadScene = false;
                OnReloadScene();
            }
        }

        List<FutureTask<XrResult>> toRemovePA = new List<FutureTask<XrResult>>();
        List<FutureTask<(XrResult, AnchorManager.Anchor)>> toRemoveFPA = new List<FutureTask<(XrResult, AnchorManager.Anchor)>>();

        void UpdateTasks()
        {
            toRemovePA.Clear();
            // Check persist anchor tasks
            foreach (var taskTuple in tmPA.GetTasks())
            {
                var anchor = taskTuple.Item1;
                var task = taskTuple.Item2;
                if (!AnchorManager.GetSpatialAnchorName(anchor, out string name))
                    Debug.LogError("Faild to get anchor name: " + anchor);

                if (task.IsPollCompleted)
                {
                    toRemovePA.Add(task);
                    if (task.PollResult == XrResult.XR_SUCCESS)
                    {
                        var result = task.Complete();
                        if (result == XrResult.XR_SUCCESS)
                        {
                            Debug.Log("AnchorTestHandle: Persist anchor " + anchor + "=" + name + " success");
                        }
                        else
                        {
                            Debug.LogError("AnchorTestHandle: Persist anchor " + anchor + "=" + name + " failed: " + result);
                        }
                    }
                    else
                    {
                        Debug.LogError("AnchorTestHandle: Persist anchor " + anchor + "=" + name + " failed: " + task.PollResult);
                    }
                }
            }
            foreach (var task in toRemovePA)
            {
                tmPA.RemoveTask(task);
            }
            toRemovePA.Clear();

            // Check create from persisted anchor tasks
            toRemoveFPA.Clear();
            foreach (var taskTuple in tmFPA.GetTasks())
            {
                var paName = taskTuple.Item1;
                var task = taskTuple.Item2;
                if (task.IsPollCompleted)
                {
                    toRemoveFPA.Add(task);
                    if (task.PollResult == XrResult.XR_SUCCESS)
                    {
                        var result = task.Complete();
                        if (result.Item1 == XrResult.XR_SUCCESS)
                        {
                            Debug.Log("AnchorTestHandle: Create anchor from persisted anchor " + paName + " success");
                            if (paName == persistedAnchor1Name)
                            {
                                anchor1 = result.Item2;
                            }
                            else if (paName == persistedAnchor2Name)
                            {
                                anchor2 = result.Item2;
                            }
                        }
                        else
                        {
                            Debug.LogError("AnchorTestHandle: Create anchor from persisted anchor " + paName + " failed: " + result.Item1);
                            if (paName == persistedAnchor1Name)
                            {
                                anchor1FromPA = null;
                            }
                            else if (paName == persistedAnchor2Name)
                            {
                                anchor2FromPA = null;
                            }
                        }
                    }
                    else
                    {
                        Debug.LogError("AnchorTestHandle: Create anchor from persisted anchor " + paName + " failed: " + task.PollResult);
                        if (paName == persistedAnchor1Name)
                        {
                            anchor1FromPA = null;
                        }
                        else if (paName == persistedAnchor2Name)
                        {
                            anchor2FromPA = null;
                        }
                    }
                }
            }
            foreach (var task in toRemoveFPA)
            {
                tmFPA.RemoveTask(task);
            }
            toRemoveFPA.Clear();
        }

        void Update()
        {
            PerformEditorTest();

            if (!isAnchorSupported)
            {
                UpdateUIInteractions();
                return;
            }

            UpdateTasks();

            // Check persisted files
            CheckExportedFiles();

            // Enumerate persisted anchors
            EnumeratePersistedAnchors();

            // Update anchors if persisted anchors are existed
            UpdateAnchorsIfPersistExist();

            // Update trackable anchors' pose
            UpdateTrackableAnchorsPose();

            // Update UI interactions
            UpdateUIInteractions();
        }

        public void OnFloor()
        {
            Debug.Log("AnchorTestHandle: OnFloor()");
            if (xrInputSubsystem == null)
            {
                Debug.LogError("xrInputSubsystem is null");
                statusOrigin.text = "xrInputSubsystem is null";
                return;
            }


            OnResetObj();
            OnClearAllAnchors();
            if (xrInputSubsystem.TrySetTrackingOriginMode(TrackingOriginModeFlags.Floor))
            {
                statusOrigin.text = "Set tracking origin to floor. Reset Obj and anchors";
            }
            else
            {
                statusOrigin.text = "Fail to set tracking origin to floor";
            }
        }

        public void OnDevice()
        {
            Debug.Log("AnchorTestHandle: OnDevice()");
            if (xrInputSubsystem == null)
            {
                Debug.LogError("AnchorTestHandle: xrInputSubsystem is null");
                statusOrigin.text = "xrInputSubsystem is null";
                return;
            }


            OnResetObj();
            OnClearAllAnchors();
            if (xrInputSubsystem.TrySetTrackingOriginMode(TrackingOriginModeFlags.Device))
            {
                statusOrigin.text = "Set tracking origin to device. Reset Obj and anchors";
            }
            else
            {
                statusOrigin.text = "Fail to set tracking origin to device";
            }
        }

        public void OnReloadScene()
        {
            Debug.Log("AnchorTestHandle: OnReloadScene()");
            UnityEngine.SceneManagement.SceneManager.LoadScene(UnityEngine.SceneManagement.SceneManager.GetActiveScene().name);
        }
    }
}
