/*
 * ExternalReceiver
 * https://sabowl.sakura.ne.jp/gpsnmeajp/
 *
 * MIT License
 * 
 * Copyright (c) 2019 gpsnmeajp
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
#pragma warning disable 0414,0219
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Profiling;


namespace EVMC4U
{
    public class DeviceReceiver : MonoBehaviour, IExternalReceiver
    {
        const int arrayMax = 16;

        [Header("DeviceReceiver v1.1")]
        [SerializeField, Label("動作状況")]
        private string StatusMessage = "";  //Inspector表示用
        [SerializeField, Label("現実のトラッキング位置を反映")]
        public bool RealPosition = false;

#if EVMC4U_JA
        [Header("トラッキング設定")]
#else
        [Header("Tracking Config")]
#endif
        public string[] Serials = new string[arrayMax];
        public Transform[] Transforms = new Transform[arrayMax];

#if EVMC4U_JA
        [Header("トラッキングデバイス情報モニタ(表示用)")]
#else
        [Header("Tracking Device Monitor")]
#endif
        public string[] Types = new string[arrayMax];
        public Vector3[] Vector3s = new Vector3[arrayMax];

#if EVMC4U_JA
        [Header("デイジーチェーン")]
#else
        [Header("Daisy Chain")]
#endif
        public GameObject[] NextReceivers = new GameObject[1];

        private ExternalReceiverManager externalReceiverManager = null;
        bool shutdown = false;

        Dictionary<string, int> SerialIndexes = new Dictionary<string, int>();
        int ListIndex = 0;

        //メッセージ処理一時変数struct(負荷対策)
        Vector3 pos;
        Quaternion rot;


        void Start()
        {
            externalReceiverManager = new ExternalReceiverManager(NextReceivers);
            StatusMessage = "Waiting for Master...";

            //モニタ強制
            Types = new string[Serials.Length];
            Vector3s = new Vector3[Serials.Length];

            //登録処理
            ListIndex = 0;
            for (int i = 0; i < Serials.Length; i++)
            {
                //nullでも空白でもない場合(対象がある場合)
                if (Serials[i] != null && Serials[i] != "")
                {
                    //辞書に登録
                    SerialIndexes.Add(Serials[i], ListIndex);
                    //インデックスを更新
                    ListIndex++;
                }
            }
        }

        //デイジーチェーンを更新
        public void UpdateDaisyChain()
        {
            externalReceiverManager.GetIExternalReceiver(NextReceivers);
        }

        public void MessageDaisyChain(ref uOSC.Message message, int callCount)
        {
            //Startされていない場合無視
            if (externalReceiverManager == null || enabled == false || gameObject.activeInHierarchy == false)
            {
                return;
            }

            if (shutdown)
            {
                return;
            }

            StatusMessage = "OK";

            //異常を検出して動作停止
            try
            {
                ProcessMessage(ref message);
            }
            catch (Exception e)
            {
                StatusMessage = "Error: Exception";
                Debug.LogError(" --- Communication Error ---");
                Debug.LogError(e.ToString());
                shutdown = true;
                return;
            }

            if (!externalReceiverManager.SendNextReceivers(message, callCount))
            {
                StatusMessage = "Infinite loop detected!";
                shutdown = true;
            }
        }

        private void ProcessMessage(ref uOSC.Message message)
        {
            //メッセージアドレスがない、あるいはメッセージがない不正な形式の場合は処理しない
            if (message.address == null || message.values == null)
            {
                StatusMessage = "Bad message.";
                return;
            }

            if (!RealPosition)
            {
                if (message.address == "/VMC/Ext/Hmd/Pos"
                    && (message.values[0] is string)
                    && (message.values[1] is float)
                    && (message.values[2] is float)
                    && (message.values[3] is float)
                    && (message.values[4] is float)
                    && (message.values[5] is float)
                    && (message.values[6] is float)
                    && (message.values[7] is float)
                    )
                {
                    pos.x = (float)message.values[1];
                    pos.y = (float)message.values[2];
                    pos.z = (float)message.values[3];
                    rot.x = (float)message.values[4];
                    rot.y = (float)message.values[5];
                    rot.z = (float)message.values[6];
                    rot.w = (float)message.values[7];

                    devideUpdate("HMD", (string)message.values[0], pos, rot);
                    //Debug.Log("HMD pos " + (string)message.values[0] + " : " + pos + "/" + rot);
                }
                // v2.2
                else if (message.address == "/VMC/Ext/Con/Pos"
                    && (message.values[0] is string)
                    && (message.values[1] is float)
                    && (message.values[2] is float)
                    && (message.values[3] is float)
                    && (message.values[4] is float)
                    && (message.values[5] is float)
                    && (message.values[6] is float)
                    && (message.values[7] is float)
                    )
                {
                    pos.x = (float)message.values[1];
                    pos.y = (float)message.values[2];
                    pos.z = (float)message.values[3];
                    rot.x = (float)message.values[4];
                    rot.y = (float)message.values[5];
                    rot.z = (float)message.values[6];
                    rot.w = (float)message.values[7];

                    devideUpdate("Controller", (string)message.values[0], pos, rot);
                    //Debug.Log("Con pos " + (string)message.values[0] + " : " + pos + "/" + rot);
                }
                // v2.2
                else if (message.address == "/VMC/Ext/Tra/Pos"
                    && (message.values[0] is string)
                    && (message.values[1] is float)
                    && (message.values[2] is float)
                    && (message.values[3] is float)
                    && (message.values[4] is float)
                    && (message.values[5] is float)
                    && (message.values[6] is float)
                    && (message.values[7] is float)
                    )
                {
                    pos.x = (float)message.values[1];
                    pos.y = (float)message.values[2];
                    pos.z = (float)message.values[3];
                    rot.x = (float)message.values[4];
                    rot.y = (float)message.values[5];
                    rot.z = (float)message.values[6];
                    rot.w = (float)message.values[7];

                    devideUpdate("Tracker", (string)message.values[0], pos, rot);
                    //Debug.Log("Tra pos " + (string)message.values[0] + " : " + pos + "/" + rot);
                }
            }
            else {
                if (message.address == "/VMC/Ext/Hmd/Pos/Local"
                    && (message.values[0] is string)
                    && (message.values[1] is float)
                    && (message.values[2] is float)
                    && (message.values[3] is float)
                    && (message.values[4] is float)
                    && (message.values[5] is float)
                    && (message.values[6] is float)
                    && (message.values[7] is float)
                    )
                {
                    pos.x = (float)message.values[1];
                    pos.y = (float)message.values[2];
                    pos.z = (float)message.values[3];
                    rot.x = (float)message.values[4];
                    rot.y = (float)message.values[5];
                    rot.z = (float)message.values[6];
                    rot.w = (float)message.values[7];

                    devideUpdate("HMD", (string)message.values[0], pos, rot);
                    //Debug.Log("HMD pos " + (string)message.values[0] + " : " + pos + "/" + rot);
                }
                // v2.2
                else if (message.address == "/VMC/Ext/Con/Pos/Local"
                    && (message.values[0] is string)
                    && (message.values[1] is float)
                    && (message.values[2] is float)
                    && (message.values[3] is float)
                    && (message.values[4] is float)
                    && (message.values[5] is float)
                    && (message.values[6] is float)
                    && (message.values[7] is float)
                    )
                {
                    pos.x = (float)message.values[1];
                    pos.y = (float)message.values[2];
                    pos.z = (float)message.values[3];
                    rot.x = (float)message.values[4];
                    rot.y = (float)message.values[5];
                    rot.z = (float)message.values[6];
                    rot.w = (float)message.values[7];

                    devideUpdate("Controller", (string)message.values[0], pos, rot);
                    //Debug.Log("Con pos " + (string)message.values[0] + " : " + pos + "/" + rot);
                }
                // v2.2
                else if (message.address == "/VMC/Ext/Tra/Pos/Local"
                    && (message.values[0] is string)
                    && (message.values[1] is float)
                    && (message.values[2] is float)
                    && (message.values[3] is float)
                    && (message.values[4] is float)
                    && (message.values[5] is float)
                    && (message.values[6] is float)
                    && (message.values[7] is float)
                    )
                {
                    pos.x = (float)message.values[1];
                    pos.y = (float)message.values[2];
                    pos.z = (float)message.values[3];
                    rot.x = (float)message.values[4];
                    rot.y = (float)message.values[5];
                    rot.z = (float)message.values[6];
                    rot.w = (float)message.values[7];

                    devideUpdate("Tracker", (string)message.values[0], pos, rot);
                    //Debug.Log("Tra pos " + (string)message.values[0] + " : " + pos + "/" + rot);
                }

            }
        }

        void devideUpdate(string type, string serial, Vector3 pos, Quaternion rot)
        {
            //辞書に登録済み
            if (SerialIndexes.ContainsKey(serial))
            {
                int i = SerialIndexes[serial];
                //配列を更新
                Types[i] = type;
                Vector3s[i] = pos;

                if (i < Transforms.Length && Transforms[i] != null) 
                {
                    Transforms[i].localPosition = pos;
                    Transforms[i].localRotation = rot;
                }
            }
            else
            {
                //最大を超えたら登録しない
                if (ListIndex < Serials.Length)
                {
                    //辞書に未登録

                    //辞書に登録
                    Serials[ListIndex] = serial;
                    SerialIndexes.Add(serial, ListIndex);
                    //インデックスを更新
                    ListIndex++;
                }
            }
        }
    }
}