using Milimoe.FunGame.Core.Interface.Base;
using Milimoe.FunGame.Core.Library.Constant;
using Milimoe.FunGame.Core.Service;

namespace Milimoe.FunGame.Core.Library.Common.Network
{
    public class Socket : IClientSocket, ISocketHeartBeat
    {
        public System.Net.Sockets.Socket Instance { get; }
        public SocketRuntimeType Runtime => SocketRuntimeType.Client;
        public Guid Token { get; set; } = Guid.Empty;
        public string ServerIP { get; } = "";
        public int ServerPort { get; } = 0;
        public string ServerName { get; } = "";
        public string ServerNotice { get; } = "";
        public int HeartBeatFaileds => _HeartBeatFaileds;
        public bool Connected => Instance != null && Instance.Connected;
        public bool Receiving => _Receiving;
        public bool SendingHeartBeat => _SendingHeartBeat;

        private Task? SendingHeartBeatTask;
        private Task? ReceivingTask;
        private Task? WaitHeartBeatReply;

        private bool _Receiving = false;
        private bool _SendingHeartBeat = false;
        private int _HeartBeatFaileds = 0;

        private Socket(System.Net.Sockets.Socket Instance, string ServerIP, int ServerPort)
        {
            this.Instance = Instance;
            this.ServerIP= ServerIP;
            this.ServerPort = ServerPort;
            this.StartSendingHeartBeat();
        }

        public static Socket Connect(string IP, int Port = 22222)
        {
            System.Net.Sockets.Socket? socket = SocketManager.Connect(IP, Port);
            if (socket != null) return new Socket(socket, IP, Port);
            else throw new ConnectFailedException();
        }
        
        public SocketResult Send(SocketMessageType type, params object[] objs)
        {
            if (Instance != null)
            {
                if (SocketManager.Send(type, Token, objs) == SocketResult.Success)
                {
                    return SocketResult.Success;
                }
                return SocketResult.Fail;
            }
            return SocketResult.NotSent;
        }

        public SocketObject Receive()
        {
            try
            {
                SocketObject result = SocketManager.Receive();
                if (result.SocketType == SocketMessageType.HeartBeat)
                {
                    if (WaitHeartBeatReply != null && !WaitHeartBeatReply.IsCompleted) WaitHeartBeatReply.Wait(1);
                    _HeartBeatFaileds = 0;
                }
                return result;
            }
            catch
            {
                throw new SocketWrongInfoException();
            }
        }

        public void BindEvent(Delegate Method, bool Remove = false)
        {
            if (!Remove)
            {
                SocketManager.SocketReceive += (SocketManager.SocketReceiveHandler)Method;
            }
            else
            {
                SocketManager.SocketReceive -= (SocketManager.SocketReceiveHandler)Method;
            }
        }

        public void CheckHeartBeatFaileds()
        {
            if (HeartBeatFaileds >= 3) Close();
        }

        public void Close()
        {
            StopSendingHeartBeat();
            StopReceiving();
            Instance?.Close();
        }

        public void ResetHeartBeatFaileds()
        {
            _HeartBeatFaileds = 0;
        }

        public void StartReceiving(Task t)
        {
            _Receiving = true;
            ReceivingTask = t;
        }

        private void StartSendingHeartBeat()
        {
            _SendingHeartBeat = true;
            SendingHeartBeatTask = Task.Factory.StartNew(SendHeartBeat);
        }

        private void StopReceiving()
        {
            _Receiving = false;
            ReceivingTask?.Wait(1);
            ReceivingTask = null;
        }

        private void StopSendingHeartBeat()
        {
            _SendingHeartBeat = false;
            SendingHeartBeatTask?.Wait(1);
            SendingHeartBeatTask = null;
        }

        private void SendHeartBeat()
        {
            Thread.Sleep(100);
            while (Connected)
            {
                if (!SendingHeartBeat) _SendingHeartBeat= true;
                // 发送心跳包
                if (Send(SocketMessageType.HeartBeat) == SocketResult.Success)
                {
                    WaitHeartBeatReply = Task.Run(() =>
                    {
                        Thread.Sleep(4000);
                        AddHeartBeatFaileds();
                    });
                }
                else AddHeartBeatFaileds();
                Thread.Sleep(20000);
            }
            _SendingHeartBeat = false;
        }

        private void AddHeartBeatFaileds()
        {
            // 超过三次没回应心跳，服务器连接失败。
            if (_HeartBeatFaileds++ >= 3)
                throw new LostConnectException();
        }

        public static string GetTypeString(SocketMessageType type)
        {
            return SocketManager.GetTypeString(type);
        }
    }
}
