using System;
using System.Threading;
using System.Net;
using System.Text;
using System.Linq;
using System.Collections.Specialized;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.WebRTC;
using UnityEngine.UI;
using Phoenix;
using DesktopVision.DVSocket;
using Unity.Plastic.Newtonsoft.Json;
using UnityEngine.Assertions;

namespace DesktopVision.DVWebRTC
{

    
    /// <summary>
    /// This class is used to pass connection events.
    /// </summary>
    public class ConnectionEventArgs : EventArgs
    {
        /// <summary>
        /// The connection message
        /// </summary>
        public string message { get; set; }
        /// <summary>
        /// Any errors encountered when using the connection class
        /// </summary>
        public Exception connectionError { get; set; }
    }

    /// <summary>
    /// The ConnectionManager class is used to handle WebRTC connections to Desktop Vision
    /// </summary>
    public class DVConnectionManager : MonoBehaviour
    {
        /// <summary>
        /// The prefab that will display a computer/stream
        /// </summary>
        public GameObject receivePrefab;
        /// <summary>
        /// The localparticipant which will send events
        /// </summary>
        public string localParticpant;
        /// <summary>
        /// The audio source to play stream audio from
        /// </summary>
        [SerializeField] public AudioSource audio;
        /// <summary>
        /// The raw image where the video will play
        /// </summary>
        [SerializeField] RawImage overlay;

        /// <summary>
        /// The Event Dispatcher used to send out connection events
        /// </summary>
        /// <remarks>
        /// Use this to listen for connection events and display or hide the video
        /// </remarks>
        public event EventHandler<ConnectionEventArgs> Dispatch;
        enum ProtocolOption
        {
            Default,
            UDP,
            TCP
        }

        private Dictionary<string, Peer> peerConnections = new Dictionary<string, Peer>();
        private DelegateOnNegotiationNeeded sourcePeerConnectionOnNegotiationNeeded;
        private int width = 1000, height = 1000, waiting = 0;
        private string localCandidateId;
        private RoomOptions _roomOptions;
        private Socket _socket;
        private Channel _roomChannel;
        private Presence _presence;
        private List<string> usersJoined, usersLeft;
        private SynchronizationContext syncContext;
        private List<string> users;
        private double MilliTimeStamp(DateTime TheDate)
        {
            DateTime d1 = new DateTime(1970, 1, 1);
            DateTime d2 = TheDate.ToUniversalTime();
            TimeSpan ts = new TimeSpan(d2.Ticks - d1.Ticks);

            return ts.TotalMilliseconds;
        }

        public virtual void DispatchEvent(ConnectionEventArgs e)
        {
            EventHandler<ConnectionEventArgs> handler = Dispatch;
            Dispatch?.Invoke(this, e);
        }

        private void Start()
        {
            Debug.Log("START WEBRTC");
            usersJoined = new List<string>();
            syncContext = SynchronizationContext.Current;
            var thread = new Thread(() =>
            {
                SynchronizationContext.SetSynchronizationContext(syncContext);
            });
            thread.Start();
            //initialize webrtc
            try
            {
                WebRTC.Initialize();
            }
            catch (Exception e)
            {
                Debug.Log(e.Message);
            }
        }

        /// <summary>
        /// Connect to the Desktop Vision Computer
        /// </summary>/// <param name="roomOptions">The room options to connect to</param>
        public void StartConnection(RoomOptions roomOptions)
        {
            Debug.Log($"START CONNECTION ${roomOptions.uid} {roomOptions.room_id}");
            _roomOptions = roomOptions;
            StartCoroutine(WebRTC.Update());
            localParticpant = _roomOptions.uid;
            //create socket connection
            joinSocket(_roomOptions, out _socket);
            ConnectToRoom(out _roomChannel);
            Presence presence = new Presence(_roomChannel);
            presence.OnJoin += onJoinCallback;
            presence.OnLeave += onLeaveCallback;
            //listen for signaling events
            _socket.OnMessage += (e) => handleSocketMessage(e, _roomChannel);

        }

        private void ConnectToRoom(out Channel _roomChannel)
        {
            //join the room channel to receive signalling events
            joinRoomChannel(_roomOptions, _socket, out _roomChannel);
        }
        private void OnApplicationQuit()
        {
            Debug.Log("DESTROY");
            endRTC();
        }

        /// <summary>
        /// End the connection to the Desktop Vision Computer
        /// </summary>
        public void endRTC()
        {
            try
            {
                _roomChannel.Leave();
                _socket.Disconnect();
                foreach (var peer in peerConnections)
                {
                    peer.Value.Dispose();
                }
            }
            catch (Exception e)
            {
                Debug.Log(e.Message);
            }
        }

        private void joinSocket(RoomOptions _roomOptions, out Socket socket)
        {
            Dictionary<string, string> socketParams = new Dictionary<string, string>();

            string socketAddress = "wss://meetings.dev/socket";
            Socket.Options socketOptions = new Socket.Options(new JsonMessageSerializer());
            WebsocketSharpFactory socketFactory = new WebsocketSharpFactory();

            socketParams.Add("auth_token", _roomOptions.auth_token);
            socket = new Socket(socketAddress, socketParams, socketFactory, socketOptions);
            socket.Connect();
        }

        private void handleSocketMessage(Message message, Channel room)
        {
            syncContext.Post((object state) =>
            {
                if (message.Event == "signal")
                {
                    /*
                        parse singla message and set local description
                        then signal back with an answer
                    */
                    DVPayload payload = JsonConvert.DeserializeObject<DVPayload>($"{message.Payload}");
                    SignalingMessage dvSignal = JsonConvert.DeserializeObject<SignalingMessage>(payload.body);

                    string localUser = _roomOptions.uid;
                    string signalFrom = dvSignal.from;
                    string signalTo = dvSignal.to[0];
                    string signalString = dvSignal.signals[0];
                    SDPSignal parsedSignal = JsonConvert.DeserializeObject<SDPSignal>(signalString);
                    string signalType = parsedSignal.type;

                    if (signalType != "candidate") Debug.Log($"SIGNAL RECEIEVED: {signalType} \nFROM: {signalFrom}");
                    if (signalFrom == localUser) return;



                    Peer peerConnection;
                    initPeer(signalFrom, out peerConnection);

                    WebRTC.Update();

                    if (signalType == "candidate")
                    {
                        Debug.Log("CANDIDATE");
                        CandidateSignal cSignal = JsonConvert.DeserializeObject<CandidateSignal>(signalString);
                        RTCIceCandidate signalCandidate = new RTCIceCandidate(cSignal.candidate);
                        var candidate = new PeerMessage { candidate = signalCandidate, type = "candidate" };

                        peerConnection.OnMessage(candidate);
                    }
                    else if (signalType == "transceiverRequest")
                    {
                        Debug.Log("TRANSCEIVER Request");
                        peerConnection.addTransceiver();
                    }
                    else if (signalType == "renegotiate")
                    {
                        Debug.Log("RENEGOTOATE");
                        peerConnection.createOffer();
                    }
                    else if (signalType == "offer")
                    {
                        Debug.Log("OFFER");
                        RTCSessionDescription remoteDescription = new RTCSessionDescription();
                        remoteDescription.type = RTCSdpType.Offer;
                        remoteDescription.sdp = parsedSignal.sdp;
                        PeerMessage offer = new PeerMessage { description = remoteDescription };

                        peerConnection.OnMessage(offer);
                    }
                    else if (signalType == "answer")
                    {
                        Debug.Log("ANSWER");
                        RTCSessionDescription remoteDescription = new RTCSessionDescription();
                        remoteDescription.type = RTCSdpType.Answer;
                        remoteDescription.sdp = parsedSignal.sdp;
                        PeerMessage answer = new PeerMessage { description = remoteDescription };

                        peerConnection.OnMessage(answer);
                    }
                }
            }, null);
        }

        /// <summary>
        /// Send a message to the Desktop Vision Computer
        /// </summary>
        public void SendPeerMessage(string message)
        {
            foreach (var item in peerConnections)
            {
                item.Value.SendMsg(message);
            }
        }

        /// <summary>
        /// Send an ice candidate to the Desktop Vision Computer
        /// </summary>
        public void PostCandidate(PeerMessage message, Peer peerConnection)
        {
            DVCandidate iceSignal = new DVCandidate(message.candidate);
            string candidateString = JsonConvert.SerializeObject(iceSignal);

            string from = _roomOptions.uid;
            string[] to = { peerConnection.uid };
            string[] candidateSignals = { candidateString };
            double createdAt = (double)(Math.Floor(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds * 1000));
            SignalingMessage candidateSignal = new SignalingMessage(to, from, candidateSignals, createdAt);
            string candidateSignalString = JsonConvert.SerializeObject(candidateSignal);
            DVPayload candidatePayload = new DVPayload(candidateSignalString);
            _roomChannel.Push("signal", candidatePayload).Receive(ReplyStatus.Ok, reply => Debug.Log(reply));
        }

        /// <summary>
        /// Send a signaling message to the Desktop Vision Computer
        /// </summary>
        public void PostMessage(PeerMessage message, Peer peerConnection)
        {
            MeetingsDevSessionDescription meetingSignal = new MeetingsDevSessionDescription(message.description, message.type);
            string localSignalString = JsonConvert.SerializeObject(meetingSignal);

            string from = _roomOptions.uid;
            double createdAt = MilliTimeStamp(DateTime.Now);
            string[] to = { peerConnection.uid };
            string[] signals = { localSignalString };

            SignalingMessage returnSignal = new SignalingMessage(to, from, signals, createdAt);
            string returnSignalString = JsonConvert.SerializeObject(returnSignal);
            DVPayload returnPayload = new DVPayload(returnSignalString);
            _roomChannel.Push("signal", returnPayload).Receive(ReplyStatus.Ok, reply => Debug.Log(reply));
        }
        private void joinRoomChannel(RoomOptions _roomOptions, Socket socket, out Channel roomChannel)
        {
            string roomAddress = $"room:{_roomOptions.project_id}-{_roomOptions.room_id}";
            roomChannel = socket.Channel(
                roomAddress,
                null
            );
            roomChannel.Join();
        }

        private void onJoinCallback(string uid, Presence.MetadataContainer currentPresence, Presence.MetadataContainer newPresence)
        {
            syncContext.Post((object state) =>
            {
                string localUser = _roomOptions.uid;
                if (localUser == uid) return;
                Debug.Log($"USER JOINED THE ROOM {uid}");
                bool exists = usersJoined.Contains(uid);
                if (exists)
                {
                    Debug.Log("user exists");
                    return;
                }
                Peer peerConnection;
                initPeer(uid, out peerConnection);

                usersJoined.Add(uid);
                if (peerConnection.initiator)
                {
                    Debug.Log($"LOCAL SHOULD OFFER");
                    peerConnection.createOffer();
                }
                else
                {
                    //remote is less than local, expect signal
                    Debug.Log($"REMOTE SHOULD OFFER");
                }
            }, null);
        }


        private void initPeer(string uid, out Peer peerConnection)
        {
            Debug.Log("WEBRTC INIT PEER");
            if (!peerConnections.ContainsKey(uid))
            {
                string localUser = _roomOptions.uid;
                Debug.Log($"LOCAL USER: {localUser}");
                int comparison = uid.CompareTo(localUser);
                bool initiator = comparison > 0;
                Debug.Log($"initiator: {initiator}");
                RTCConfiguration config = GetSelectedSdpSemantics(_roomOptions);
                Debug.Log($"SDP SEMANTICS: {config.iceServers}");
                Debug.Log($"INIT WITH RECIEVE: {receivePrefab} CONFIG: {config} UID: {uid} INITIATOR: {initiator} OVERLAY: {overlay} AUDIO: {audio}");
                //    INIT WITH RECIEVE: Screen Object (UnityEngine.GameObject) CONFIG: Unity.WebRTC.RTCConfiguration UID: 2a96ae6f-525f-43f5-abb6-ecf466aad66f INITIATOR: False OVERLAY: Screen (UnityEngine.UI.RawImage) AUDIO: AudioObject (UnityEngine.AudioSource)
                peerConnection = new Peer(this, receivePrefab, config, uid, initiator, overlay, audio);
                Debug.Log($"PEER CONNECTION: {peerConnection}");
                peerConnections.Add(uid, peerConnection);
            }
            else
            {
                peerConnection = peerConnections[uid];
            }
        }

        private void onLeaveCallback(string uid, Presence.MetadataContainer currentPresence, Presence.MetadataContainer newPresence)
        {
            UnityEngine.Debug.Log($"user: {uid} disconnected");
            UnityEngine.Debug.Log($"user: {uid} disconnected");

            //remove peer with uid
            if (peerConnections.ContainsKey(uid))
            {
                Peer peerConnection = peerConnections[uid];
                peerConnection.Dispose();
                peerConnections.Remove(uid);
            }

            usersJoined = usersJoined.Where(user => uid != user).ToList();
        }
        private static RTCConfiguration GetSelectedSdpSemantics(RoomOptions _roomOptions)
        {
            RTCConfiguration config = default;
            config.iceServers = _roomOptions.ice_servers;
            return config;
        }
        private void HandleReceiveMessage(byte[] bytes)
        {
            var message = System.Text.Encoding.UTF8.GetString(bytes);
            UnityEngine.Debug.Log(message);
        }

        private static void OnCreateSessionDescriptionError(RTCError error)
        {
            Debug.LogError($"Error Detail Type: {error.message}");
        }
    }

}