using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Unity.WebRTC;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.UI;
using WebSocketSharp;

public class VideoChatMediaStreaming : MonoBehaviour
{
    [SerializeField] private string clientId;
    [SerializeField] private string IPAddress;
    [SerializeField] private Camera cameraStream;
    [SerializeField] private RawImage sourceImage;
    [SerializeField] private RawImage destinationImage;
    [SerializeField] private List<RawImage> recievedImages = new List<RawImage>();

    private Dictionary<string, RTCPeerConnection> pcs = new Dictionary<string, RTCPeerConnection>();

    private VideoStreamTrack videoTrack;

    private bool hasRecievedOffer = false;
    private SessionDescription recievedSessDescTemp;
    private string recievedChannelId;

    private WebSocket ws;
    private WebCamTexture webCamTexture;

    private int recievedImageCounter = 0;

    private void Start()
    {
        //InitWebCamTexture();
        InitClient(IPAddress, 8080);
    }

    private void Update()
    {
        if (hasRecievedOffer)
        {
            hasRecievedOffer = !hasRecievedOffer;
            StartCoroutine(CreateAnswer(pcs[recievedChannelId], recievedChannelId));
        }
    }

    private void OnDestroy()
    {
        videoTrack.Stop();

        foreach (var connection in pcs.Values)
        {
            connection.Close();
        }

        ws.Close();
    }

    private void InitWebCamTexture()
    {
        WebCamDevice myDevice = new WebCamDevice();
        myDevice = WebCamTexture.devices.Last();
        webCamTexture = new WebCamTexture(myDevice.name);
        sourceImage.texture = webCamTexture;
        webCamTexture.Play();
    }
    private void InitClient(string ip, int port)
    {
        ws = new WebSocket($"ws://{ip}:{port}/{nameof(VideoChatMediaStreamService)}");

        ws.OnMessage += (sender, e) =>
        {
            var signalingMessage = new SignalingMessage(e.Data);

            switch(signalingMessage.Type)
            {
                case SignalingMessageType.OFFER:

                    if (clientId == signalingMessage.ChannelId.Substring(1, 1))
                    {
                        Debug.Log(clientId + " - Got <b>Offer</b> with channel ID " + signalingMessage.ChannelId + " from Maximus: " + signalingMessage.Message);
                        recievedChannelId = signalingMessage.ChannelId;
                        recievedSessDescTemp = SessionDescription.FromJson(signalingMessage.Message);
                        hasRecievedOffer = true; 
                    }

                    break;

                case SignalingMessageType.ANSWER:

                    if (clientId == signalingMessage.ChannelId.Substring(0, 1))
                    {
                        Debug.Log(clientId + " - Got <b>Answer</b> with channel ID " + signalingMessage.ChannelId + " from Maximus: " + signalingMessage.Message);

                        var recievedAnswerSessionDescTemp = SessionDescription.FromJson(signalingMessage.Message);
                        RTCSessionDescription answerSessionDesc = new RTCSessionDescription();
                        answerSessionDesc.type = RTCSdpType.Answer;
                        answerSessionDesc.sdp = recievedAnswerSessionDescTemp.Sdp;

                        pcs[signalingMessage.ChannelId].SetRemoteDescription(ref answerSessionDesc);
                    }

                    break;

                case SignalingMessageType.CANDIDATE:

                    if (clientId == signalingMessage.ChannelId.Substring(1, 1))
                    {
                        Debug.Log(clientId + " - Got <b>Candidate</b> with channel ID " + signalingMessage.ChannelId + " from Maximus: " + signalingMessage.Message);

                        var candidateInit = CandidateInit.FromJson(signalingMessage.Message);
                        RTCIceCandidateInit init = new RTCIceCandidateInit();
                        init.sdpMid = candidateInit.SdpMid;
                        init.sdpMLineIndex = candidateInit.SdpMLineIndex;
                        init.candidate = candidateInit.Candidate;
                        RTCIceCandidate candidate = new RTCIceCandidate(init);

                        pcs[signalingMessage.ChannelId].AddIceCandidate(candidate);
                    }

                    break;

                default:

                    if (e.Data.Contains("|"))
                    {
                        var connectionsIds = e.Data.Split('|');
                        foreach (var connectionId in connectionsIds)
                        {
                            if (connectionId.Contains(clientId))
                            {
                                pcs.Add(connectionId, CreatePeerConnection(connectionId));
                            }
                        }
                    }
                    else
                    {
                        clientId = e.Data;

                    }


                    break;
            }
        };
        ws.Connect();

        var gfxType = SystemInfo.graphicsDeviceType;
        var format = WebRTC.GetSupportedRenderTextureFormat(gfxType);

        //RenderTexture rt = new RenderTexture(1280, 720, 0, format);

        //videoTrack = new VideoStreamTrack(sourceImage.texture);

        videoTrack = cameraStream.CaptureStreamTrack(1280, 720);
        sourceImage.texture = cameraStream.targetTexture;

        StartCoroutine(WebRTC.Update());
    }

    private RTCPeerConnection CreatePeerConnection(string id)
    {
        var pc = new RTCPeerConnection();
        pc.OnIceCandidate = candidate =>
        {
            var candidateInit = new CandidateInit()
            {
                SdpMid = candidate.SdpMid,
                SdpMLineIndex = candidate.SdpMLineIndex ?? 0,
                Candidate = candidate.Candidate
            };
            ws.Send("CANDIDATE!" + id + "!" + candidateInit.ConvertToJson());
        };
        pc.OnIceConnectionChange = state =>
        {
            Debug.Log(state);
        };
        pc.OnNegotiationNeeded = () =>
        {
            StartCoroutine(CreateOffer(pc, id));
        };
        pc.OnTrack = e =>
        {
            if (e.Track is VideoStreamTrack track)
            {
                track.OnVideoReceived += tex =>
                {
                    recievedImages[recievedImageCounter].texture = tex;
                    recievedImageCounter++;
                };
            }
        };

        return pc;
    }

    private IEnumerator CreateOffer(RTCPeerConnection pc, string id)
    {
        var offer = pc.CreateOffer();
        yield return offer;

        var offerDesc = offer.Desc;
        var localDescOp = pc.SetLocalDescription(ref offerDesc);
        yield return localDescOp; 

        var offerSessionDesc = new SessionDescription()
        {
            SessionType = offerDesc.type.ToString(),
            Sdp = offerDesc.sdp
        };

        ws.Send("OFFER!" + id + "!" + offerSessionDesc.ConvertToJson());
    }

    private IEnumerator CreateAnswer(RTCPeerConnection pc, string id)
    {
        RTCSessionDescription offerSessionDesc = new RTCSessionDescription();
        offerSessionDesc.type = RTCSdpType.Offer;
        offerSessionDesc.sdp = recievedSessDescTemp.Sdp;

        var remoteDescOp = pc.SetRemoteDescription(ref offerSessionDesc);
        yield return remoteDescOp;

        var answer = pc.CreateAnswer();
        yield return answer;

        var answerDesc = answer.Desc;
        var localDescOp = pc.SetLocalDescription(ref answerDesc);
        yield return localDescOp;

        var answerSessionDesc = new SessionDescription()
        {
            SessionType = answerDesc.type.ToString(),
            Sdp = answerDesc.sdp
        };

        ws.Send("ANSWER!" + id + "!" + answerSessionDesc.ConvertToJson());
    }

    public void Call()
    {
        foreach (var connection in pcs)
        {
            if (connection.Key.Substring(0, 1) == clientId)
            {
                connection.Value.AddTrack(videoTrack);
            }
        }
    }
}
