using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR;
using UnityEngine.UI;
using DesktopVision.DVWebRTC;
using TMPro;

namespace DesktopVision
{

    /// <summary>
    /// This class is used to store the computer data for use with Desktop Vision
    /// </summary>
    public class DVComputerRecord
    {
        /// <summary>
        /// The channel name of the computer
        /// </summary>
        public string channel_name;
        /// <summary>
        /// The readable name of the computer record.
        /// e.g. "My Computer"
        /// </summary>
        public string computerName;
        public DVComputerRecord(string channel_name, string computerName)
        {
            this.channel_name = channel_name;
            this.computerName = computerName;
        }
    }

    /// <summary>
    /// This class is used to store coordinates and webrt for use with Desktop Vision
    /// </summary>
    public class DVCursorEvent : EventArgs
    {
        /// <summary>
        /// The coordinates of the cursor event in relative x/y
        /// </summary>
        public Vector2 Coordinates { get; set; }
        /// <summary>
        /// The type of cursor event
        /// </summary>
        /// <remarks>
        public string EventName { get; set; }
        /// <summary>
        /// The webrtc data channel the cursor event will be sent on
        /// </summary>
        public DVConnectionManager webrtc { get; set; }
    }
    /// <summary>
    /// 
    /// </summary>
    public class DVKeyEvent : EventArgs
    {
        /// <summary>
        /// The type of key event
        /// </summary>
        public string EventName { get; set; }
        /// <summary>
        /// The key code of the key event
        /// </summary>
        public key key { get; set; }
        /// <summary>
        /// if the shift key is pressed
        /// </summary>
        public bool shiftKey { get; set; }
        /// <summary>
        /// if the alt key is pressed
        /// </summary>
        public bool altKey { get; set; }
        /// <summary>
        /// if the control key is pressed
        /// </summary>
        public bool ctrlKey { get; set; }
        /// <summary>
        /// The webrtc data channel the cursor event will be sent on
        /// </summary>
        public DVConnectionManager webrtc { get; set; }
    }

    /// <summary>
    /// This class is used to manage a Computer object in the scene
    /// </summary>
    public class DVComputer : MonoBehaviour
    {
        /// <summary>
        /// The object layer to look for events on
        /// </summary>
        [SerializeField] LayerMask objectLayer;

        /// <summary>
        /// Buttons used with managed computers for generating codes & refreshing the screen
        /// </summary>
        [SerializeField] public GameObject ManagementButtons;
        /// <summary>
        /// Button used to generate codes on a managed computer
        /// </summary>
        [SerializeField] public Button DVButton;
        /// <summary>
        /// Buttons used with managed computers for refreshing the screen
        /// </summary>
        [SerializeField] public Button RefreshButton;
        /// <summary>
        /// The screen object which contains the various screens & icons
        /// </summary>
        [SerializeField] public GameObject ScreenObject;
        /// <summary>
        /// The video screen which displays a video and is interacted with
        /// </summary>
        [SerializeField] public GameObject VideoScreen;

        /// <summary>
        /// button to spawn a keyboard
        /// </summary>
        [SerializeField] private Button keyboardButton;

        /// <summary>
        /// the keyboard prefab
        /// </summary>
        [SerializeField] public GameObject keyboardPrefab;

        /// <summary>
        /// The screensaver panel used to display the logo
        /// </summary>
        [SerializeField] public GameObject Screensaver;

        /// <summary>
        /// The text panel used to display any necessary text
        /// </summary>
        [SerializeField] public GameObject TextPanel;

        /// <summary>
        /// The title text for the text panel
        /// </summary>
        [SerializeField] public TMP_Text Title;

        /// <summary>
        /// The subtitle text for the text panel
        /// </summary>
        [SerializeField] public TMP_Text Subtitle;

        /// <summary>
        ///  The webrtc data channel the events will be sent on
        /// </summary>
        [HideInInspector] public DVConnectionManager webrtc;

        /// <summary>
        /// The event handler for listening to cursor events
        /// </summary>
        /// <example>
        /// <code>
        /// DVComputer.OnCursorEvent += (object sender, DVCursorEvent e) => {});
        /// </code>
        /// </example>
        public event EventHandler<DVCursorEvent> DispatchCursor;

        /// <summary>
        /// The event handler for listening to key events
        /// </summary>
        /// <example>
        /// <code>
        /// DVComputer.OnKeyEvent += (object sender, DVKeyEvent e) => {}); 
        /// </code>
        /// </example>
        public event EventHandler<DVKeyEvent> DispatchKeyboard;

        /// <summary>
        /// Whether the screen should automatically track the camera
        /// </summary>
        public bool cameraTracking;

        /// <summary>
        /// Whether or not the screen is being moved around
        /// </summary>
        /// <remarks>
        /// This is triggered by the MoveIcon.cs script
        /// </remarks>
        public bool movingComputer;

        /// <summary>
        /// The size of the screen
        /// </summary>
        public float screenSize = 1.0f;

        private Transform leftController;
        private Transform rightController;
        private Transform activeController;
        private DVXRController leftC, rightC;
        private DVKeyboard keyboardScript;
        private bool keyboardActive = false;
        private float screenDistance;

        protected virtual void DispatchKeyboardEvent(KeyboardEventArgs e)
        {
            DVKeyEvent dvEvent = new DVKeyEvent();
            dvEvent.EventName = e.eventName;
            dvEvent.shiftKey = e.shift;
            dvEvent.altKey = e.alt;
            dvEvent.ctrlKey = e.ctrl;
            dvEvent.key = e.key;
            dvEvent.webrtc = webrtc;
            EventHandler<DVKeyEvent> handler = DispatchKeyboard;
            handler?.Invoke(this, dvEvent);
        }

        protected virtual void DispatchCursorEvent(ControllerEventArgs e)
        {
            DVCursorEvent dvEvent = new DVCursorEvent();
            dvEvent.EventName = e.EventName;
            dvEvent.Coordinates = e.Coordinates;
            dvEvent.webrtc = webrtc;
            EventHandler<DVCursorEvent> handler = DispatchCursor;
            handler?.Invoke(this, dvEvent);
        }

        void Start()
        {
            webrtc = GetComponentInParent<DVConnectionManager>();
            createControllers();
            createKB();
            Transform desktopTransform = transform;
            Vector3 cameraPosition = Camera.main.transform.position;
            cameraPosition.y = desktopTransform.position.y;
            desktopTransform.LookAt(2 * desktopTransform.position - cameraPosition, Vector3.up);
        }


        void Update()
        {
            try
            {
                leftC.Update();
                rightC.Update();
            }
            catch (Exception e)
            {
                Debug.Log(e.Message);
            }
        }

        void LateUpdate()
        {
            if (cameraTracking)
            {
                Transform desktopTransform = transform;
                Vector3 cameraPosition = Camera.main.transform.position;
                cameraPosition.y = desktopTransform.position.y;
                desktopTransform.LookAt(2 * desktopTransform.position - cameraPosition, Vector3.up);
            }

            if (this.activeController)
            {
                if (movingComputer && screenDistance > 0)
                {
                    Ray ray = new Ray(this.activeController.position, this.activeController.forward);
                    Vector3 point = ray.GetPoint(screenDistance);
                    transform.position = point;
                    transform.LookAt(2 * transform.position - ray.origin, Vector3.up);
                }
                else
                {
                    screenDistance = Vector3.Distance(this.activeController.position, transform.position);
                }
            }
        }
        void createControllers()
        {
            GameObject lController = GameObject.Find("LeftHand Controller");
            GameObject rController = GameObject.Find("RightHand Controller");
            leftController = lController.GetComponent<Transform>();
            rightController = rController.GetComponent<Transform>();

            leftC = new DVXRController("left", objectLayer, leftController, VideoScreen);
            rightC = new DVXRController("right", objectLayer, rightController, VideoScreen);

            rightC.Dispatch += ListenControllerEvents;
            leftC.Dispatch += ListenControllerEvents;
        }
        void createKB()
        {
            float width = VideoScreen.transform.localScale.x;
            float height = VideoScreen.transform.localScale.y;
            Vector3 position = new Vector3(x: 0, y: -height / 2 - 0.1f - 0.25f, z: -0.1f);
            keyboardPrefab = Instantiate(keyboardPrefab, position, Quaternion.identity);
            keyboardPrefab.transform.SetParent(transform, worldPositionStays: false);
            keyboardPrefab.transform.localScale = new Vector3(1, 1, 1);
            keyboardPrefab.SetActive(false);

            keyboardButton.onClick.AddListener(toggleKb);

            keyboardScript = keyboardPrefab.GetComponentInChildren<DVKeyboard>();
            keyboardScript.Dispatch += ListenKeyboardEvents;
        }

        void toggleKb()
        {
            keyboardActive = !keyboardActive;
            keyboardPrefab.SetActive(keyboardActive);

            if (this.activeController)
            {
                //create ray from controller
                Ray ray = new Ray(this.activeController.position, this.activeController.forward);
                Vector3 point = ray.GetPoint(1f);
                keyboardPrefab.transform.position = point;
                keyboardPrefab.transform.LookAt(2 * keyboardPrefab.transform.position - ray.origin, Vector3.up);
                Debug.Log($"{keyboardActive} kb active");
                Debug.Log($"{point}");
            }
        }


        void ListenControllerEvents(object sender, ControllerEventArgs e)
        {
            if (e.EventName != "dragMove")
            {
                this.activeController = e.controller;
            }
            if (!e.hoveringDesktop)
            {
                return;
            }
            Vector2 coordinates = e.Coordinates;
            coordinates.y = 1 - coordinates.y;
            e.Coordinates = coordinates;
            DispatchCursorEvent(e);
        }

        void ListenKeyboardEvents(object sender, KeyboardEventArgs e)
        {
            if (e.key.id == "CLOSE")
            {
                toggleKb();
            }
            else
            {
                Debug.Log(e.key.id);
                DispatchKeyboardEvent(e);
            }
        }

        /// <summary>
        /// This method displays text & a title on the screen
        /// </summary>
        public void setText(string title, string subtitle)
        {
            TextPanel.SetActive(true);
            Screensaver.SetActive(false);
            VideoScreen.SetActive(false);
            Title.text = title;
            Subtitle.text = subtitle;

            Vector3 newScale = new Vector3(screenSize*2, screenSize, screenSize);
            ScreenObject.transform.localScale = newScale;
        }


        ///	<summary>
        /// This method displays the video on the screen
        /// </summary>
        public void setVideo()
        {
            TextPanel.SetActive(false);
            Screensaver.SetActive(false);
            VideoScreen.SetActive(true);
        }


        /// <summary>
        /// This method displays the screensaver on the screen
        /// </summary>
        public void setScreenSaver()
        {
            TextPanel.SetActive(false);
            Screensaver.SetActive(true);
            VideoScreen.SetActive(false);

            Vector3 newScale = new Vector3(screenSize*2, screenSize, screenSize);
            ScreenObject.transform.localScale = newScale;
        }
    }
}