using GLTFast;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using UnityEngine;

public class SceneManagementGLTF : MonoBehaviour
{
    /// <summary>
    /// Types of World Analysis to Use
    /// </summary>
    public WorldAnalysisFactory.WorldAnalysisType _WorldAnalysisType;
    /// <summary>
    /// Path to GLTF File (relative to streaming assets in editor, else persistentdatapath)
    /// </summary>
    public string _PathToGLTF;
    
    /// <summary>
    /// List of trackables and anchors in the AR Scene
    /// </summary>
    private Dictionary<string, Transform> m_trackablesAndAnchorsInARScene;

    /// <summary>
    /// Current world analysis
    /// </summary>
    private WorldAnalysisInterface m_worldAnalysis; 

    /// <summary>
    /// Unity Start Method
    /// </summary>
    protected async void Start()
    {
        m_trackablesAndAnchorsInARScene = new Dictionary<string, Transform>();
        await LoadGltfBinaryFromMemory();
        Transform loaded = this.transform.GetChild(0);
        FindWorldStorageTransform(loaded);
        m_worldAnalysis = WorldAnalysisFactory.CreateWorldAnalysis(_WorldAnalysisType, this.gameObject);
        //Subscribe
        foreach(KeyValuePair<string , Transform> toSubscribe in m_trackablesAndAnchorsInARScene)
        {
            int validity = 100000; //10s
            string subscriptionUUID;
            // TODO : if only one : subscribeToPose, if multiple subscribetoPoses
            m_worldAnalysis.SubscribeToPose(null, toSubscribe.Key, ETSI.ARF.OpenAPI.WorldAnalysis.Mode_WorldAnalysis.DEVICE_TO_TRACKABLES, PoseCallback, ref validity, out subscriptionUUID); //TODO : find a value for the token parameter.
        }
    }

    /// <summary>
    /// The callback function used in the susbscription method. It instantiate a prefab or update its pose based on the current situation.
    /// </summary>
    /// <param name="result"></param> 
    /// <param name="pose"> The pose to update in the SM</param>
    public void PoseCallback(WorldAnalysisInterface.PoseEstimationResult result, ETSI.ARF.OpenAPI.WorldAnalysis.Pose pose)
    {
        if (pose.EstimationState != ETSI.ARF.OpenAPI.WorldAnalysis.PoseEstimationState.OK)
        {
            Debug.Log("State Not Ok for " + pose.Uuid +  "  "+ pose.EstimationState.ToString() + " " + pose.InstructionInfo);
            return;
        }

        if (pose.Value.Type == ETSI.ARF.OpenAPI.WorldAnalysis.PoseValueType.VECTOR_QUATERNION)
        {
            ETSI.ARF.OpenAPI.WorldAnalysis.VectorQuaternionPoseValue value = (ETSI.ARF.OpenAPI.WorldAnalysis.VectorQuaternionPoseValue)pose.Value;
            if (m_trackablesAndAnchorsInARScene.ContainsKey(pose.Uuid.ToString()))
            {
                m_trackablesAndAnchorsInARScene[pose.Uuid.ToString()].transform.position =  WorldAnalysisUnityHelper.ConvertETSIVector3ToUnity(value.Position);
                m_trackablesAndAnchorsInARScene[pose.Uuid.ToString()].transform.rotation =  WorldAnalysisUnityHelper.ConvertETSIARFQuaternionToUnity(value.Rotation);
            }
            else
            {
                Debug.LogWarning("Callback for unwanted uuid");
            }
        }
        else
        {
            Debug.LogWarning("Pose value type not supported yet :" +pose.Value.Type); // Todo : manage other types
        }
    }


    /// <summary>
    /// Load a GLTF from local memory according to _PathToGLTF
    /// </summary>
    /// <returns>ascync method</returns>
    protected async Task LoadGltfBinaryFromMemory()
    {
#if UNITY_EDITOR
        string prefix = Application.streamingAssetsPath;
#else
        string prefix = Application.persistentDataPath;
#endif 
        var filePath = prefix + "/" + _PathToGLTF;

        Debug.Log("PATH : " + filePath);

        byte[] data = File.ReadAllBytes(filePath);
        Debug.Log("File Size " + data.Length);
        var gltf = new GltfImport();
        bool success = await gltf.LoadGltfBinary(
            data,
            // The URI of the original data is important for resolving relative URIs within the glTF
            new Uri(filePath)
            );
        GameObjectInstantiator instantiator = new GameObjectInstantiator(gltf, this.transform);
        if (success)
        {
            success = await gltf.InstantiateMainSceneAsync(instantiator);
        }
    }

    /// <summary>
    /// In the loaded GLTF File : find all World Storage Trackables and Anchors
    /// </summary>
    /// <param name="transform">Loaded GLTF Transform</param>s
    protected void FindWorldStorageTransform(Transform trGLTF)
    {
        if (trGLTF.name.StartsWith("ws:"))
        {
            string id = trGLTF.name.Substring(3);
            Debug.Log("Add " + id + "  " + trGLTF.name);
            m_trackablesAndAnchorsInARScene.Add(id, trGLTF);
        }
        foreach(Transform child in trGLTF)
        {
            FindWorldStorageTransform(child);
        }
    }
}