using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using ZLog;

namespace ZSocket
{
    /// <summary>
    /// 粘包解析器
    /// </summary>
    public class RecvPacketPraser
    {
        const int MAGIC_SIZE = 4;
        const int TOTAL_SIZE_SIZE = 4;

        readonly static byte[] s_Magic = new byte[] { 0x5a, 0x57, 0x41, 0x49 };

        public enum ParserState { PacketMagic, PacketVersion, PacketSize, PacketBody, PacketBodyDone }
        public ParserState State { get; private set; } = ParserState.PacketMagic;
        public MemoryStream MemoryStream { get; private set; }
        public byte Version { get; private set; }

        int packetSize;
        byte[] magicCache = new byte[MAGIC_SIZE];
        byte[] totalSizeCache = new byte[TOTAL_SIZE_SIZE];

        public bool Parse(CircularBuffer buffer)
        {
            switch (State)
            {
                case ParserState.PacketMagic:
                    if (buffer.Length < MAGIC_SIZE)
                    {
                        return false;
                    }

                    buffer.Read(magicCache, 0, MAGIC_SIZE);

                    for (int i = 0; i < MAGIC_SIZE; ++i)
                    {
                        if (magicCache[i] != s_Magic[i])
                        {
                            throw new Exception("Magic error");
                        }
                    }

                    State = ParserState.PacketVersion;
                    break;
                case ParserState.PacketVersion:
                    if (buffer.Length < 1)
                    {
                        return false;
                    }

                    Version = buffer.ReadByte();

                    State = ParserState.PacketSize;
                    break;
                case ParserState.PacketSize:
                    if (buffer.Length < TOTAL_SIZE_SIZE)
                    {
                        return false;
                    }

                    buffer.Read(totalSizeCache, 0, TOTAL_SIZE_SIZE);
                    packetSize = totalSizeCache[0] << 24 | totalSizeCache[1] << 16 | totalSizeCache[2] << 8 | totalSizeCache[3];
                    packetSize -= MAGIC_SIZE + 1 + TOTAL_SIZE_SIZE;

                    if (packetSize == 0)
                    {
                        return true;
                    }
                    //if (packetSize > PACKET_CACHE_SIZE)
                    //{
                    //    throw new Exception($"Size error {packetSize}");
                    //}
                    //else if (packetSize == 0)
                    //{
                    //    return true;
                    //}

                    State = ParserState.PacketBody;
                    break;
                case ParserState.PacketBody:
                    if (buffer.Length < packetSize)
                    {
                        return false;
                    }

                    //buffer.Read(packetCache, 0, readSize);
                    //MemoryStream = new MemoryStream(packetCache, 0, packetSize, true, true);

                    MemoryStream = new MemoryStream();

                    buffer.Read(MemoryStream, packetSize);
                    MemoryStream.Seek(0, SeekOrigin.Begin);

                    State = ParserState.PacketBodyDone;
                    break;
                case ParserState.PacketBodyDone:
                    State = ParserState.PacketMagic;
                    break;
            }
            return true;
        }
    }

    /// <summary>
    /// 错误码
    /// </summary>
    public class ErrorCode
    {
        public const int ERR_Success = 0;
        // 110000 以上避免与SocketError冲突
        public const int ERR_PeerDisconnect = 110001;
        public const int ERR_SocketError = 110002;
        public const int ERR_RpcTimeout = 110003;
        public const int ERR_ConnectionNotStarted = 110004;
        public const int ERR_RecvTimeout = 110005;
        public const int ERR_ConnectionDisposed = 110006;
    }

    /// <summary>
    /// Socket句柄
    /// </summary>
    public class SocketHandler
    {
        public const int RECV_BUFFER_SIZE = 8192;

        static int idGen = 0;
        private int id;
        public int Id => id;

        public Action<byte, MemoryStream> RecvCallback;
        public Action<int> ErrCallback;

        public bool Disposed { get; internal set; } = false;

        Socket socket;
        bool isSending = false;
        byte[] buffer = new byte[RECV_BUFFER_SIZE];
        object sendLock = new object();
        int errorCode = 0;

        RecvPacketPraser recvParser = new RecvPacketPraser();
        CircularBuffer sendBuffer = new CircularBuffer(RECV_BUFFER_SIZE);
        CircularBuffer recvBuffer = new CircularBuffer(RECV_BUFFER_SIZE);

        public SocketHandler(Socket socket)
        {
            id = ++idGen;
            this.socket = socket;
            //socket.BeginReceive(buffer, 0, RECV_BUFFER_SIZE, SocketFlags.None, OnReceive, socket);
        }

        internal void BeginReceive()
        {
            socket.BeginReceive(buffer, 0, RECV_BUFFER_SIZE, SocketFlags.None, OnReceive, socket);
        }

        public void Send(byte[] data, int offset, int size, byte version = 0x01)
        {
            lock (sendLock)
            {
                sendBuffer.WriteByte((byte)'Z');
                sendBuffer.WriteByte((byte)'W');
                sendBuffer.WriteByte((byte)'A');
                sendBuffer.WriteByte((byte)'I');

                sendBuffer.WriteByte(version);

                // 写入数据
                int headerSize = 9;
                sendBuffer.WriteInt(size + headerSize);
                sendBuffer.Write(data, offset, size);
            }
        }

        public void Send(byte[] data, byte version = 0x01)
        {
            Send(data, 0, data.Length, version);
        }

        public void Send(string msgType, string msg, byte version = 0x01)
        {
            Send(Encoding.UTF8.GetBytes(msg), version);
        }

        void OnSendComplete(IAsyncResult result)
        {
            try
            {
                int sendSize = socket.EndSend(result, out SocketError err);

                if (err != SocketError.Success)
                {
                    errorCode = (int)err;
                    return;
                }

                Log.Debug($"SocketHandler Sent {sendSize} {err}");

                sendBuffer.Dump(sendSize);

                isSending = false;
            }
            catch (Exception e)
            {
                errorCode = ErrorCode.ERR_SocketError;
                Log.Error(e.ToString());
            }
        }

        void OnReceive(IAsyncResult result)
        {
            try
            {
                int recvSize = socket.EndReceive(result, out SocketError err);

                if (err != SocketError.Success)
                {
                    errorCode = (int)err;
                    return;
                }

                if (recvSize > 0)
                {
                    Log.Debug($"SocketHandler Recv {recvSize} {err}");
                    recvBuffer.Write(buffer, 0, recvSize);
                }
                else
                {
                    errorCode = ErrorCode.ERR_PeerDisconnect;
                    return;
                }
            }
            catch (Exception e)
            {
                errorCode = ErrorCode.ERR_SocketError;
                Log.Error(e.ToString());
                return;
            }

            if (socket.Connected)
            {
                socket.BeginReceive(buffer, 0, RECV_BUFFER_SIZE, SocketFlags.None, OnReceive, socket);
            }
        }

        void OnError(int err)
        {
            SocketManager.Disconnect(this);
            try
            {
                ErrCallback.Invoke(err);
            }
            catch (Exception e)
            {
                Log.Error(e.ToString());
            }
        }

        void StartSend()
        {
            if (!socket.Connected)
            {
                isSending = false;
                return;
            }

            if (isSending)
            {
                return;
            }

            if (sendBuffer.Length == 0)
            {
                isSending = false;
                return;
            }

            isSending = true;

            int sendSize = sendBuffer.ChunkSize - sendBuffer.FirstIndex;
            if (sendSize > sendBuffer.Length)
            {
                sendSize = sendBuffer.Length;
            }

            socket.BeginSend(sendBuffer.First, sendBuffer.FirstIndex, sendSize, SocketFlags.None, OnSendComplete, socket);
        }

        void StartRecv()
        {
            if (socket == null || !socket.Connected)
            {
                return;
            }

            if (recvBuffer.Length == 0)
            {
                return;
            }

            while (recvParser.Parse(recvBuffer))
            {
                //if (recvParser.MemoryStream == null)
                if (recvParser.State != RecvPacketPraser.ParserState.PacketBodyDone)
                {
                    continue;
                }

                RecvCallback.Invoke(recvParser.Version, recvParser.MemoryStream);
            }
        }

        internal void Tick()
        {
            if (Disposed)
            {
                return;
            }

            if (errorCode != 0)
            {
                OnError(errorCode);
                return;
            }

            if (socket == null || !socket.Connected)
            {
                return;
            }

            StartSend();
            StartRecv();
        }
    }

    /// <summary>
    /// 托管Socket的管理器
    /// </summary>
    public static class SocketManager
    {
        static Dictionary<int, Socket> mapSockets = new Dictionary<int, Socket>();
        static Dictionary<int, SocketHandler> socketHandlers = new Dictionary<int, SocketHandler>();
        static List<int> listIdToRemove = new List<int>();
        static ConcurrentQueue<(SocketHandler, int)> errorQueue = new ConcurrentQueue<(SocketHandler, int)>();

        public static SocketHandler Connect(IPEndPoint endPoint)
        {
            Socket socket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

            SocketHandler handler = new SocketHandler(socket);
            mapSockets.Add(handler.Id, socket);
            socketHandlers.Add(handler.Id, handler);

            socket.BeginConnect(endPoint, OnBeginConnect, handler);

            return handler;
        }

        public static void Disconnect(SocketHandler handler)
        {
            if (handler == null || handler.Disposed)
            {
                return;
            }

            if (!mapSockets.TryGetValue(handler.Id, out Socket socket))
            {
                return;
            }

            try
            {
                if (socket.Connected)
                {
                    socket.Disconnect(false);
                }
            }
            catch (SocketException e)
            {
                Log.Error(e.ToString());
                handler.ErrCallback.Invoke(e.ErrorCode);
            }
            catch (Exception e)
            {
                Log.Error(e.ToString());
                handler.ErrCallback.Invoke(ErrorCode.ERR_SocketError);
            }

            handler.Disposed = true;
            listIdToRemove.Add(handler.Id);
            Log.Debug("SocketManager Disconnected");
        }

        public static void Tick()
        {
            while (errorQueue.TryDequeue(out var item))
            {
                Disconnect(item.Item1);
                item.Item1.ErrCallback(item.Item2);
            }

            foreach (var handler in socketHandlers.Values)
            {
                handler.Tick();
            }

            foreach (int id in listIdToRemove)
            {
                if (mapSockets.ContainsKey(id))
                {
                    mapSockets.Remove(id);
                }
                if (socketHandlers.ContainsKey(id))
                {
                    socketHandlers.Remove(id);
                }
            }
            listIdToRemove.Clear();
        }

        static void OnBeginConnect(IAsyncResult result)
        {
            SocketHandler handler = result.AsyncState as SocketHandler;
            if (!mapSockets.TryGetValue(handler.Id, out Socket socket))
            {
                Log.Error("SocketManager Unmanaged socket connected");
                return;
            }

            try
            {
                socket.EndConnect(result);
                Log.Debug("SocketManager Connected Async");

                handler.BeginReceive();
            }
            catch (SocketException e)
            {
                Log.Error(e.ToString());
                errorQueue.Enqueue((handler, e.ErrorCode));
            }
            catch (Exception e)
            {
                Log.Error(e.ToString());
                errorQueue.Enqueue((handler, ErrorCode.ERR_SocketError));
            }
        }
    }
}
