using Mirror;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using UnityEngine;

namespace LightReflectiveMirror
{
    public partial class LightReflectiveMirrorTransport : Transport
    {
        public override bool ServerActive() => _isServer;
        public override bool Available() => _connectedToRelay;
        public override void ClientConnect(Uri uri) => ClientConnect(uri.Host);
        public override int GetMaxPacketSize(int channelId = 0) => clientToServerTransport.GetMaxPacketSize(channelId);
        public override bool ClientConnected() => _isClient;

        public override void ServerLateUpdate()
        {
            if (_directConnectModule != null)
                _directConnectModule.directConnectTransport.ServerLateUpdate();
        }

        public override string ServerGetClientAddress(int connectionId)
        {
            if (_connectedRelayClients.TryGetBySecond(connectionId, out int relayId))
                return relayId.ToString();

            if (_connectedDirectClients.TryGetBySecond(connectionId, out int directId))
                return "DIRECT-" + directId;

            // Shouldn't ever get here.
            return "?";
        }

        public override void ClientEarlyUpdate()
        {
            clientToServerTransport.ClientEarlyUpdate();

            if (_directConnectModule != null)
                _directConnectModule.directConnectTransport.ClientEarlyUpdate();
        }

        public override void ClientLateUpdate()
        {
            clientToServerTransport.ClientLateUpdate();

            if (_directConnectModule != null)
                _directConnectModule.directConnectTransport.ClientLateUpdate();
        }

        public override void ServerEarlyUpdate()
        {
            if (_directConnectModule != null)
                _directConnectModule.directConnectTransport.ServerEarlyUpdate();
        }

        public override void ClientConnect(string address)
        {
            if (!Available() || !int.TryParse(address, out _cachedHostID))
            {
                Debug.Log("Not connected to relay or invalid server id!");
                OnClientDisconnected?.Invoke();
                return;
            }

            if (_isClient || _isServer)
                throw new Exception("Cannot connect while hosting/already connected!");

            var room = GetServerForID(_cachedHostID);

            if (!useLoadBalancer || room.relayInfo.Address == serverIP)
            {
                int pos = 0;
                _directConnected = false;
                _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.JoinServer);
                _clientSendBuffer.WriteInt(ref pos, _cachedHostID);
                _clientSendBuffer.WriteBool(ref pos, _directConnectModule != null);

                if (GetLocalIp() == null)
                    _clientSendBuffer.WriteString(ref pos, "0.0.0.0");
                else
                    _clientSendBuffer.WriteString(ref pos, GetLocalIp());

                _isClient = true;

                clientToServerTransport.ClientSend(0, new System.ArraySegment<byte>(_clientSendBuffer, 0, pos));
            }
            else
            {
                StartCoroutine(JoinOtherRelayAndMatch(room));
            }
        }

        public override void ClientDisconnect()
        {
            _isClient = false;

            int pos = 0;
            _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.LeaveRoom);

            clientToServerTransport.ClientSend(0, new ArraySegment<byte>(_clientSendBuffer, 0, pos));

            if (_directConnectModule != null)
                _directConnectModule.ClientDisconnect();
        }

        public override void ClientSend(int channelId, ArraySegment<byte> segment)
        {
            if (_directConnected)
            {
                _directConnectModule.ClientSend(segment, channelId);
            }
            else
            {
                int pos = 0;
                _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.SendData);
                _clientSendBuffer.WriteBytes(ref pos, segment.Array.Take(segment.Count).ToArray());
                _clientSendBuffer.WriteInt(ref pos, 0);

                clientToServerTransport.ClientSend(channelId, new ArraySegment<byte>(_clientSendBuffer, 0, pos));
            }
        }

        public override bool ServerDisconnect(int connectionId)
        {
            if (_connectedRelayClients.TryGetBySecond(connectionId, out int relayId))
            {
                int pos = 0;
                _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.KickPlayer);
                _clientSendBuffer.WriteInt(ref pos, relayId);
                return true;
            }

            if (_connectedDirectClients.TryGetBySecond(connectionId, out int directId))
                return _directConnectModule.KickClient(directId);

            return false;
        }

        public override void ServerSend(int connectionId, int channelId, ArraySegment<byte> segment)
        {
            if (_directConnectModule != null && _connectedDirectClients.TryGetBySecond(connectionId, out int directId))
            {
                _directConnectModule.ServerSend(directId, segment, channelId);
            }
            else
            {
                int pos = 0;
                _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.SendData);
                _clientSendBuffer.WriteBytes(ref pos, segment.Array.Take(segment.Count).ToArray());
                _clientSendBuffer.WriteInt(ref pos, _connectedRelayClients.GetBySecond(connectionId));

                clientToServerTransport.ClientSend(channelId, new ArraySegment<byte>(_clientSendBuffer, 0, pos));
            }
        }

        public override void ServerStart()
        {
            if (!Available())
            {
                Debug.Log("Not connected to relay! Server failed to start.");
                return;
            }

            if (_isClient || _isServer)
            {
                Debug.Log("Cannot host while already hosting or connected!");
                return;
            }

            _isServer = true;
            _connectedRelayClients = new BiDictionary<int, int>();
            _currentMemberId = 1;
            _connectedDirectClients = new BiDictionary<int, int>();

            var keys = new List<IPEndPoint>(_serverProxies.GetAllKeys());

            for (int i = 0; i < keys.Count; i++)
            {
                _serverProxies.GetByFirst(keys[i]).Dispose();
                _serverProxies.Remove(keys[i]);
            }

            int pos = 0;
            _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.CreateRoom);
            _clientSendBuffer.WriteInt(ref pos, maxServerPlayers);
            _clientSendBuffer.WriteString(ref pos, serverName);
            _clientSendBuffer.WriteBool(ref pos, isPublicServer);
            _clientSendBuffer.WriteString(ref pos, extraServerData);
            // If we have direct connect module, and our local IP isnt null, tell server. Only time local IP is null is on cellular networks, such as IOS and Android.
            _clientSendBuffer.WriteBool(ref pos, _directConnectModule != null ? GetLocalIp() != null ? true : false : false);

            if (_directConnectModule != null && GetLocalIp() != null)
            {
                _clientSendBuffer.WriteString(ref pos, GetLocalIp());
                // Transport port will be NAT port + 1 for the proxy connections.
                _directConnectModule.StartServer(useNATPunch ? _NATIP.Port + 1 : -1);
            }
            else
                _clientSendBuffer.WriteString(ref pos, "0.0.0.0");

            if (useNATPunch)
            {
                _clientSendBuffer.WriteBool(ref pos, true);
                _clientSendBuffer.WriteInt(ref pos, 0);
            }
            else
            {
                _clientSendBuffer.WriteBool(ref pos, false);
                _clientSendBuffer.WriteInt(ref pos, _directConnectModule == null ? 1 : _directConnectModule.SupportsNATPunch() ? _directConnectModule.GetTransportPort() : 1);
            }

            clientToServerTransport.ClientSend(0, new ArraySegment<byte>(_clientSendBuffer, 0, pos));
        }

        public override void ServerStop()
        {
            if (_isServer)
            {
                _isServer = false;
                int pos = 0;
                _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.LeaveRoom);

                clientToServerTransport.ClientSend(0, new ArraySegment<byte>(_clientSendBuffer, 0, pos));

                if (_directConnectModule != null)
                    _directConnectModule.StopServer();

                var keys = new List<IPEndPoint>(_serverProxies.GetAllKeys());

                for (int i = 0; i < keys.Count; i++)
                {
                    _serverProxies.GetByFirst(keys[i]).Dispose();
                    _serverProxies.Remove(keys[i]);
                }
            }
        }

        public override Uri ServerUri()
        {
            UriBuilder builder = new UriBuilder
            {
                Scheme = "LRM",
                Host = serverId.ToString()
            };

            return builder.Uri;
        }

        public override void Shutdown()
        {
            _isAuthenticated = false;
            _isClient = false;
            _isServer = false;
            _connectedToRelay = false;
            clientToServerTransport.Shutdown();
        }
    }
}