// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

using System.Collections.Generic;
using System.Linq;
using UnityEngine;

#if UNITY_WSA || UNITY_STANDALONE_WIN
using UnityEngine.Windows.Speech;
#endif

namespace HoloToolkit.Unity.SpatialMapping
{
    [RequireComponent(typeof(RemoteMeshTarget))]
    public partial class RemoteMappingManager : Singleton<RemoteMappingManager>
    {
        [Tooltip("Key to press in editor to enable spatial mapping over the network.")]
        public KeyCode RemoteMappingKey = KeyCode.N;

        [Tooltip("Keyword for sending meshes from HoloLens to Unity over the network.")]
        public string SendMeshesKeyword = "send meshes";

#if UNITY_EDITOR || UNITY_STANDALONE
        /// <summary>
        /// Receives meshes collected over the network.
        /// </summary>
        private RemoteMeshTarget remoteMeshTarget;
#endif

#if UNITY_WSA || UNITY_STANDALONE_WIN
        /// <summary>
        /// Used for voice commands.
        /// </summary>
        private KeywordRecognizer keywordRecognizer;

        /// <summary>
        /// Collection of supported keywords and their associated actions.
        /// </summary>
        private Dictionary<string, System.Action> keywordCollection;
#endif

        // Use this for initialization.
        private void Start()
        {
#if UNITY_WSA || UNITY_STANDALONE_WIN
            // Create our keyword collection.
            keywordCollection = new Dictionary<string, System.Action> { { SendMeshesKeyword, SendMeshes } };

            // Tell the KeywordRecognizer about our keywords.
            keywordRecognizer = new KeywordRecognizer(keywordCollection.Keys.ToArray());
            // Register a callback for the KeywordRecognizer and start recognizing.
            keywordRecognizer.OnPhraseRecognized += KeywordRecognizer_OnPhraseRecognized;
            keywordRecognizer.Start();
#endif

#if UNITY_EDITOR || UNITY_STANDALONE
            remoteMeshTarget = GetComponent<RemoteMeshTarget>();

            if (remoteMeshTarget != null && SpatialMappingManager.Instance.Source == null)
            {
                // Use the network-based mapping source to receive meshes in the Unity editor.
                SpatialMappingManager.Instance.SetSpatialMappingSource(remoteMeshTarget);
            }
#endif
        }

        // Called every frame by the Unity engine.
        private void Update()
        {
#if UNITY_EDITOR || UNITY_STANDALONE
            // Use the 'network' sourced mesh.  
            if (Input.GetKeyUp(RemoteMappingKey))
            {
                SpatialMappingManager.Instance.SetSpatialMappingSource(remoteMeshTarget);
            }
#endif
        }

#if UNITY_WSA || UNITY_STANDALONE_WIN
        /// <summary>
        /// Called by keywordRecognizer when a phrase we registered for is heard.
        /// </summary>
        /// <param name="args">Information about the recognition event.</param>
        private void KeywordRecognizer_OnPhraseRecognized(PhraseRecognizedEventArgs args)
        {
            System.Action keywordAction;

            if (keywordCollection.TryGetValue(args.text, out keywordAction))
            {
                keywordAction.Invoke();
            }
        }
#endif

        /// <summary>
        /// Sends the spatial mapping surfaces from the HoloLens to a remote system running the Unity editor.
        /// </summary>
        private void SendMeshes()
        {
#if !UNITY_EDITOR && UNITY_WSA
            List<MeshFilter> MeshFilters = SpatialMappingManager.Instance.GetMeshFilters();
            for (int index = 0; index < MeshFilters.Count; index++)
            {
                List<Mesh> meshesToSend = new List<Mesh>();
                MeshFilter filter = MeshFilters[index];
                Mesh source = filter.sharedMesh;
                Mesh clone = new Mesh();
                List<Vector3> verts = new List<Vector3>();
                verts.AddRange(source.vertices);
            
                for(int vertIndex=0; vertIndex < verts.Count; vertIndex++)
                {
                    verts[vertIndex] = filter.transform.TransformPoint(verts[vertIndex]);
                }

                clone.SetVertices(verts); 
                clone.SetTriangles(source.triangles, 0);
                meshesToSend.Add(clone);
                byte[] serialized = SimpleMeshSerializer.Serialize(meshesToSend);
                RemoteMeshSource.Instance.SendData(serialized);
            }
#endif
        }
    }
}