using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
using TMPro;

using Newtonsoft.Json;
using WebSocketSharp;

using ETSI.ARF.WorldStorage;
using ETSI.ARF.WorldStorage.REST;

using ETSI.ARF.WorldAnalysis;
using ETSI.ARF.WorldAnalysis.REST;
using ETSI.ARF.OpenAPI.WorldAnalysis;
using Pose = ETSI.ARF.OpenAPI.WorldAnalysis.Pose;
using static WorldAnalysisInterface;

public class ServerMaintenanceClient : MonoBehaviour
{
    static public string token = "dev";
    static public string session = "HHI";

    public GameObject floatingLogo;

    /// <summary>
    /// UUID of the node or trackable
    /// </summary>
    public string ARFMainNodeUUID;

    [Space(8)]
    public WorldStorageServer worldStorageServer;
    public WorldAnalysisREST waRESTServer;

    [Space(8)]
    public TMP_Text console;
    public GameObject asset1;

    string consoleText = "<B>ETSI ISG-ARF Client Console (STF 669)</B>\n";
    bool isConsoleText = false;

    int validity = int.MaxValue;

    // Track this trackable/anchor
    Guid uuidTarget = Guid.Empty;

    // Subscription id
    Guid uuidSub = Guid.Empty;

    // Thread safe
    bool updateTransformMatrix = false;
    bool updateTransformQuat = false;
    Matrix4x4 matrix;
    ETSI.ARF.OpenAPI.WorldAnalysis.VectorQuaternionPoseValue quat;

    // Start is called before the first frame update
    void Start()
    {
        Instance = waRESTServer;

        if (asset1 != null) asset1.GetComponent<Renderer>().material.color = Color.red;

        uuidTarget = Guid.Parse(ARFMainNodeUUID);

        ETSI.ARF.WorldStorage.REST.AdminRequest.CheckServer(worldStorageServer);
        ETSI.ARF.WorldAnalysis.REST.AdminRequest.CheckServer(waRESTServer.waServer);

        consoleText += "World Storage server: " + worldStorageServer.URI + "... connected!\n";
        consoleText += "World Analysis server: " + waRESTServer.waServer.URI + "... connected!\n";
        isConsoleText = true;

        Capability[] caps;
        if (waRESTServer.GetCapabilities(token, out caps) == CapabilityResult.OK)
        {
            waRESTServer.PrintCapabilities(caps);
            consoleText += "<B>Capabilities of World Analysis modules:</B>\n";
            if (caps.Length == 0)
            {
                consoleText += "No capabilities!\n";
            }
            else foreach (var item in caps)
                {
                    consoleText += "  Type: " + item.TrackableType.ToString() + " Acc: " + item.Accuracy.ToString() + " FPS: " + item.Framerate + "\n";
                }
            consoleText += "\n";
            isConsoleText = true;
        }

        consoleText += "Subscription of ARF objects (Trackables and World Anchrors)... OK\n";
        isConsoleText = true;

        /*
        bool isSupported = false;
        TypeWorldStorage wsType;
        Capability[] capsOfUUID;
        if (waRESTServer.GetCapability(token, uuidTarget, out isSupported, out wsType, out capsOfUUID) == CapabilityResult.OK)
        {
            waRESTServer.PrintCapabilities(capsOfUUID);
            consoleText += "\nCapabilities for UUID=" + uuidTarget.ToString() + ":\n";
            if (caps.Length == 0)
            {
                consoleText += "No capabilities!\n";
            }
            else foreach (var item in capsOfUUID)
                {
                    consoleText += " " + item.TrackableType.ToString() + " fps=" + item.Framerate + "\n";
                }
        }
        else consoleText += "No capabilities found!\n";
        isConsoleText = true;
        */
    }

    // Update is called once per frame
    bool updateAsset1 = false;
    Color asset1Color = Color.grey;
    void Update()
    {
        if (Keyboard.current.spaceKey.wasPressedThisFrame)
        {
            //webSocket.Send("PoseStart:10");
        }

        if (isConsoleText)
        {
            isConsoleText = false;
            console.text = consoleText;
        }

        if (updateAsset1)
        {
            updateAsset1 = false;
            //asset1.GetComponent<Renderer>().material.color = asset1Color;
        }

        // Thread safe
        if (updateTransformMatrix)
        {
            updateTransformMatrix = false;
            if (floatingLogo != null) floatingLogo.transform.SetPositionAndRotation(matrix.GetPosition(), matrix.rotation);
        }
        else if (updateTransformQuat)
        {
            updateTransformQuat = false;
            if (floatingLogo != null) floatingLogo.transform.position = WorldAnalysisUnityHelper.ConvertETSIVector3ToUnity(quat.Position);
            if (floatingLogo != null) floatingLogo.transform.rotation = WorldAnalysisUnityHelper.ConvertETSIARFQuaternionToUnity(quat.Rotation);
        }
    }

    public void QuitApplication()
    {
#if UNITY_EDITOR
        // Application.Quit() does not work in the editor so
        // UnityEditor.EditorApplication.isPlaying need to be set to false to end the game
        UnityEditor.EditorApplication.isPlaying = false;
#else
        Application.Quit();
#endif
    }


    private void OnDestroy()
    {
        SubscribeToPose(false);
    }

    public void SubscribeToPose(bool yes = true)
    {
        if (yes)
        {
            InformationSubscriptionResult response = waRESTServer.SubscribeToPose(token, uuidTarget, Mode_WorldAnalysis.TRACKABLES_TO_DEVICE, PoseCallback, ref validity, out uuidSub);
            if (response == InformationSubscriptionResult.OK)
            {
                SetColor(Color.yellow);
            }
            else SetColor(Color.red);
        }
        else
        {
            if (uuidSub != Guid.Empty) waRESTServer.UnsubscribeFromPose(uuidSub);
            uuidSub = Guid.Empty;
            SetColor(Color.red);
        }
    }

    public void OnButtonPoseStart()
    {
        SubscribeToPose(true);
    }

    public void OnButtonPoseStop()
    {
        SubscribeToPose(false);
    }

    public void OnButtonSendUserPose()
    {
        UnityEngine.Vector3 newPos = Camera.main.transform.position;
        UnityEngine.Quaternion newRot = Camera.main.transform.rotation;

        //Pose value
        VectorQuaternionPoseValue newPoseValue = new VectorQuaternionPoseValue();
        newPoseValue.Type = PoseValueType.VECTOR_QUATERNION;
        newPoseValue.Unit = UnitSystem.M;

        newPoseValue.Position = new ETSI.ARF.OpenAPI.WorldAnalysis.Vector3(newPos.x, newPos.y, newPos.z);
        newPoseValue.Rotation = new ETSI.ARF.OpenAPI.WorldAnalysis.Quaternion(newRot.x, newRot.y, newRot.z, newRot.w);

        Pose user = new Pose();
        user.Uuid = Guid.Parse(worldStorageServer.currentUser.UUID);
        user.Value = newPoseValue;

        string json = Newtonsoft.Json.JsonConvert.SerializeObject(user);
        Debug.Log("[WS] Sending user's pose: " + json);

        waRESTServer.WebSocketClient_Send("CurrentUserPose=" + json);
    }

    void SetColor(Color c)
    {
        asset1Color = c;
        updateAsset1 = true;
    }

    public void MessageCallback(string messageFromServer)
    {
        //
        // Is something from the WebSocket to be observed?
        //
        if (messageFromServer.StartsWith("Time="))
        {
            Debug.Log("[CLIENT] Server time is: " + messageFromServer.Split('=')[1]);

            // test the communication
            consoleText += "Server time is: " + messageFromServer.Split('=')[1] + "\n";
            isConsoleText = true;
        }
    }

    public void PoseCallback(PoseEstimationResult result, Pose pose)
    {
        switch (result)
        {
            case PoseEstimationResult.OK:

                //Debug.Log($"Updating UUID { pose.Uuid } from subscription.");
                SetColor(Color.green);

                if (pose.Value.Type == PoseValueType.MATRIX)
                {
                    // Workaround (Thread safe)
                    //MatrixPoseValue m = (MatrixPoseValue)pose.Value;
                    string v = JsonConvert.SerializeObject(pose.Value);
                    MatrixPoseValue value = JsonConvert.DeserializeObject<MatrixPoseValue>(v);

                    if (value.Transform.Count == 16)
                    {
                        matrix = WorldAnalysisUnityHelper.ConvertETSIARFTransform3DToUnity(value.Transform);
                        updateTransformMatrix = true;
                    }
                    else
                    {
                        // Ignore a wrong pose
                    }
                }
                else if (pose.Value.Type == ETSI.ARF.OpenAPI.WorldAnalysis.PoseValueType.VECTOR_QUATERNION)
                {
                    // Workaround (Thread safe)
                    //VectorQuaternionPoseValue value = (ETSI.ARF.OpenAPI.WorldAnalysis.VectorQuaternionPoseValue)pose.Value;
                    string v = JsonConvert.SerializeObject(pose.Value);
                    quat = JsonConvert.DeserializeObject<VectorQuaternionPoseValue>(v);
                    updateTransformQuat = true;
                }
                else
                {
                    Debug.LogWarning("Pose value type not supported yet :" + pose.Value.Type); // Todo : manage other types
                }

                break;

            case PoseEstimationResult.NONE:
                SetColor(Color.yellow);
                break;

            default:
                SetColor(Color.red);
                break;
        }
    }
}
