using System.Threading.Tasks;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Text;
using System.Collections;
using System.Linq;
using System;
using UnityEngine;
using UnityEngine.UI;
using Unity.Plastic.Newtonsoft.Json;
using System.Net;
using TMPro;
using UnityEngine.XR;
using UnityEngine.Networking;
using Unity.WebRTC;
using System.Threading;
using Phoenix;
using DesktopVision.DVSocket;
using UnityEngine.Assertions;
using DesktopVision.DVAuth;
using DesktopVision.DVWebRTC;

namespace DesktopVision
{
    /// <summary>
    /// The DVMenu class is used to handle the menu system.
    /// You can create computers, generate oauth codes, and refresh Desktop Vision.
    /// </summary>
    public class DVMenu : MonoBehaviour
    {
        public GameObject screenPrefab;
        [SerializeField] Button checkCodeButton;
        [SerializeField] Button refreshButton;
        [SerializeField] GameObject computerPrefab;
        [SerializeField] GameObject computerScrollView;
        [SerializeField] GameObject codeSection;
        [SerializeField] GameObject signedInSection;
        [SerializeField] GameObject menuContainer;
        [SerializeField] TMP_Text errorMessage;
        [SerializeField] TMP_Text AuthCodeTextField;

        /// <summary>
        /// Size of the computers that are spawned by the menu
        /// </summary>
        public float screenSize = 1f;
        /// <summary>
        /// The Desktop Vision APP ID used to generate oauth codes
        /// </summary>
        public string DesktopVisionAppId = "";
        /// <summary>
        /// The Desktop Vision API KEY used to generate oauth codes
        /// </summary>
        public string DesktopVisionApiKey = "";
        /// <summary>
        /// The spawn position of new computers
        /// </summary>
        public Vector3 screenPosition = new Vector3(0, 0, 0);
        /// <summary>
        /// If the menu should follow the camera or not
        /// </summary>
        public bool menuCameraTracking = true;

        /// <summary>
        /// If computers should follow the camera or not
        /// </summary>
        public bool screenCameraTracking = true;

        /// <summary>
        /// If computers should spawn in front of the camera
        /// </summary>
        public bool spawnInFrontOfCamera = false;
        private Auth dvAuth;
        private string code;
        private Boolean signedIn, menuClick;
        private List<GameObject> computerObjects = new List<GameObject>();
        private List<DVConnectionManager> webrtcControllers = new List<DVConnectionManager>();
        private List<UnityEngine.XR.InputDevice> controllers = new List<UnityEngine.XR.InputDevice>();
        private List<UnityEngine.XR.InputFeatureUsage> inputFeatures = new List<UnityEngine.XR.InputFeatureUsage>();
        private void Start()
        {
            dvAuth = new Auth();
            dvAuth.generateCode(DesktopVisionAppId, DesktopVisionApiKey);
            dvAuth.Dispatch += ListenAuthEvents;


            refreshButton.onClick.AddListener(() =>
            {
                reset();
            });
        }

        private void LateUpdate()
        {
            handleControllerButtons();

            if (menuCameraTracking)
            {
                Vector3 cameraPosition = Camera.main.transform.position;
                cameraPosition.y = transform.position.y;
                transform.LookAt(2 * transform.position - cameraPosition, Vector3.up);

                try
                {
                    //create ray from camera
                    var position = Camera.main.transform.position;
                    position.y = position.y - 0.75f;
                    Ray ray = new Ray(position, Camera.main.transform.forward);
                    Vector3 point = ray.GetPoint(2f);
                    point.y = cameraPosition.y;
                    gameObject.transform.position = point;
                    gameObject.transform.LookAt(2 * gameObject.transform.position - cameraPosition, Vector3.up);
                }
                catch (Exception e)
                {
                    Debug.Log("NO CAMERA");
                }
            }
        }

        async private void ListenAuthEvents(object sender, AuthEventArgs e)
        {
            if (e.authcode != null && e.authcode.code != null)
            {
                //display the code
                AuthCodeTextField.text = e.authcode.code;

                //set up the validate code button
                checkCodeButton.onClick.RemoveAllListeners();
                checkCodeButton.onClick.AddListener(() =>
                {
                    dvAuth.generateAccessToken(DesktopVisionAppId, DesktopVisionApiKey, e.authcode.code);
                });
            }

            if (e.authToken != null)
            {
                Debug.Log(e.authToken);
                signedIn = true;
                codeSection.SetActive(false);
                signedInSection.SetActive(true);
                errorMessage.text = "";
                List<DVComputerRecord> computers = await dvAuth.listUserComputers(e.authToken.access_token, e.authToken.uid);
                displayComputers(computers, e.authToken.access_token);
            }

            if (e.authError != null)
            {
                Debug.Log(e.authError);
                errorMessage.text = e.authError.Message;
                generateCode();
            }
        }

        private void handleControllerButtons()
        {
            UnityEngine.XR.InputDevices.GetDevicesWithCharacteristics(UnityEngine.XR.InputDeviceCharacteristics.Controller | UnityEngine.XR.InputDeviceCharacteristics.Left, controllers);

            foreach (var controller in controllers) if (controller.isValid)
                {
                    var device = controller;
                    if (device.TryGetFeatureUsages(inputFeatures))
                    {
                        foreach (var feature in inputFeatures)
                        {
                            if (feature.type == typeof(bool))
                            {
                                bool featureValue;
                                device.TryGetFeatureValue(feature.As<bool>(), out featureValue);
                                var name = feature.name;
                                if (name == "MenuButton" && featureValue && !menuClick)
                                {
                                    Debug.Log("MENU BUTTON PRESSED");
                                    menuClick = true;
                                    toggleMenu();
                                    return;
                                }
                                else if (name == "MenuButton" && !featureValue && menuClick)
                                {
                                    Debug.Log("MENU BUTTON UNPRESSED");
                                    menuClick = false;
                                    return;
                                }
                                if (name == "Menu")
                                {
                                    Debug.Log("MENU BUTTON" + featureValue);
                                }

                            }
                        }
                    }
                }
        }

        private void toggleMenu()
        {
            Debug.Log("TOGGLE MENU");
            menuContainer.SetActive(!menuContainer.activeSelf);
        }

        private void reset()
        {
            try
            {
                foreach (GameObject computer in computerObjects)
                {
                    Destroy(computer);
                }
                computerObjects.Clear();
            }
            catch (Exception e)
            {
                Debug.Log("WebRTC already disposed");
            }
            try
            {
                foreach (DVConnectionManager wrtc in webrtcControllers)
                {
                    wrtc.endRTC();
                }
                webrtcControllers.Clear();
                WebRTC.Dispose();
            }
            catch (Exception e)
            {
                Debug.Log("WebRTC already disposed");
            }
            generateCode();
            errorMessage.text = "";
            try
            {
                WebRTC.Initialize();
            }
            catch (Exception e)
            {
                Debug.Log(e.Message);
            }
        }

        private void generateCode()
        {
            codeSection.SetActive(true);
            signedInSection.SetActive(false);
            dvAuth.generateCode(DesktopVisionAppId, DesktopVisionApiKey);
            signedIn = false;
            if (!signedIn)
            {
                Task task = Task.Delay(1000 * 5 * 60).ContinueWith((task) =>
                        {
                            generateCode();
                        });
            }
        }

        private void displayComputers(List<DVComputerRecord> computers, string DesktopVisionAuthToken)
        {
            foreach (DVComputerRecord computerRecord in computers)
            {
                GameObject button = Instantiate(computerPrefab, computerScrollView.transform);
                computerObjects.Add(button);
                button.GetComponent<Button>().onClick.AddListener(async () =>
                {
                    RoomOptions options = await dvAuth.getUserComputerConnectionOptions(DesktopVisionAuthToken, computerRecord.channel_name);
                    Debug.Log(options);
                    CreateScreen(options);
                });
                button.transform.GetChild(0).GetComponent<TMP_Text>().text = computerRecord.computerName;
            }
        }


        private void CreateScreen(RoomOptions roomOptions)
        {
            // Vector3 position = new Vector3(x: Random.Range(-10.0f, 10.0f), y: 2, z: Random.Range(2f, 20.0f));

            Vector3 cameraPosition = Camera.main.transform.position;
            var position = Camera.main.transform.position;
            position.y = position.y - screenSize;
            Ray ray = new Ray(position, Camera.main.transform.forward);
            Vector3 point = ray.GetPoint(2f);
            point.y = cameraPosition.y;
            Quaternion rotation = Quaternion.identity;

            if (!spawnInFrontOfCamera)
            {
                point = screenPosition;
            }

            GameObject screen = Instantiate(screenPrefab, point, rotation);
            Vector3 newScale = new Vector3(screenSize, screenSize, 1);
            screen.transform.localScale = newScale;

            computerObjects.Add(screen);
            DVComputer computer = screen.GetComponent<DVComputer>();
            computer.screenSize = screenSize;
            computer.cameraTracking = screenCameraTracking;
            computer.DispatchCursor += ListenCursorEvents;
            computer.DispatchKeyboard += ListenKBEvents;

            DVConnectionManager webrtc = screen.GetComponent<DVConnectionManager>();
            webrtc.StartConnection(roomOptions);
            webrtcControllers.Add(webrtc);
        }


        private void ListenKBEvents(object sender, DVKeyEvent e)
        {
            Debug.Log("DV MENU RECEIVE KEY " + e.key.id);
            DVKeyboardPeerMessage controlEvent = new DVKeyboardPeerMessage(e.webrtc.localParticpant, e);
            string jsonMessage = JsonConvert.SerializeObject(controlEvent);
            e.webrtc.SendPeerMessage(jsonMessage);
        }
        private void ListenCursorEvents(object sender, DVCursorEvent e)
        {
            DVCursorPeerMessage controlEvent = new DVCursorPeerMessage(e.webrtc.localParticpant, e);
            string jsonMessage = JsonConvert.SerializeObject(controlEvent);
            e.webrtc.SendPeerMessage(jsonMessage);
        }
    }

    /// <summary>
    /// This class is used to store peer message for keybaord events
    /// </summary>
    class DVKeyboardPeerMessage
    {
        public string type, participant, key;
        public bool altKey, shiftKey, ctrlKey, metaKey;
        public DVKeyboardPeerMessage(string participant, DVKeyEvent e)
        {
            this.type = "keypress";
            this.altKey = e.altKey;
            this.ctrlKey = e.ctrlKey;
            this.shiftKey = e.shiftKey;
            this.key = e.key.id;

            this.participant = participant;
        }
    }

    /// <summary>
    /// This class is used to store peer message for cursor events
    /// </summary>
    class DVCursorPeerMessage
    {
        public string type, participant;
        public double x, y;
        public DVCursorPeerMessage(string participant, DVCursorEvent e)
        {
            string message = $"{e.Coordinates} {e.EventName}";
            this.type = e.EventName;
            this.x = e.Coordinates.x;
            this.y = e.Coordinates.y;
            this.participant = participant;
        }
    }

}