using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace FLY.Modbus.WithThread
{
    public class ServerTCP : IModbusServer,INotifyPropertyChanged
    {
        /// <summary>
        /// 通信超时, client 长时间连接上,但不通讯,会被断开
        /// </summary>
        public TimeSpan Timeout { get; set; } = TimeSpan.FromMinutes(1);

        /// <summary>
        /// 本地地址
        /// </summary>
        public IPEndPoint LocalEP { get; set; }

        /// <summary>
        /// 当前连接数
        /// </summary>
        public int ConnectedCnt { get; private set; }

        /// <summary>
        /// 工作中
        /// </summary>
        public bool IsRunning { get; set; }

        /// <summary>
        /// socket 错误信息
        /// </summary>
        public string ErrMsg { get; set; }

        /// <summary>
        /// 
        /// </summary>
        public GetValueHander GetValue { get; private set; }

        /// <summary>
        /// 
        /// </summary>
        public SetValueHander SetValue { get; private set; }

        Socket listen_socket;
        List<Client> clients = new List<Client>();

        CancellationTokenSource cts;

        public ServerTCP(IPEndPoint ep, GetValueHander getValue, SetValueHander setValue)
        {
            LocalEP = ep;
            GetValue = getValue;
            SetValue = setValue;
        }

        public void Start()
        {
            lock (this)
            {
                if (IsRunning)
                    return;
                IsRunning = true;
                cts = new CancellationTokenSource();
                Task.Factory.StartNew(OnPoll_listen, cts.Token);
            }
        }
        public void Stop()
        {
            lock (this)
            {
                if (!IsRunning)
                    return;
                cts.Cancel();
                listen_socket.Close();

                while (true)
                {
                    Client client;
                    lock (clients)
                    {
                        if (clients.Count() == 0)
                            break;
                        client = clients.First();
                        clients.Remove(client);
                    }
                    client.Close();
                }
            }
        }
        void OnPoll_listen()
        {
            listen_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            listen_socket.Bind(LocalEP);
            listen_socket.Listen(5);

            while (true)
            {
                if (cts.IsCancellationRequested)
                    goto _end;
                try
                {
                    Socket socket = listen_socket.Accept();
                    Client client = new Client(socket, this);
                    client.Closed += Client_Closed;
                    client.Start();
                    lock (client)
                    {
                        clients.Add(client);
                        ConnectedCnt = clients.Count();
                    }
                }
                catch (Exception e)
                {
                    goto _end;
                }
            }

        _end:
            IsRunning = false;
        }

        private void Client_Closed(object obj)
        {
            lock (clients)
            {
                clients.Remove(obj as Client);
            }
        }

        public delegate void ClosedHandler(object obj);
        class Client
        {


            public Socket socket;
            public CancellationTokenSource cts;
            ServerTCP server;
            public Client(Socket socket, ServerTCP server)
            {
                this.socket = socket;
                this.server = server;
            }
            public void Start()
            {
                cts = new CancellationTokenSource();
                Task.Factory.StartNew(OnPoll_client, cts.Token);
            }

            bool PullRequest(out Pack_Proto request)
            {
                //格式——————————————                
                //MBAP
                //域                         长度          描述
                //事务标识符         2byte           MODBUS请求/响应的识别码    
                //协议标识符         2byte           0=MODBUS协议
                //——长度—         2byte           以下字节的数量
                //单元标识符         1byte           串行链路或其它总线过来的识别码


                //PDU
                //功能码——         1byte
                //数据                    nbyte


                byte[] recvBytes = new byte[socket.ReceiveBufferSize];
                socket.ReceiveTimeout = (int)server.Timeout.TotalMilliseconds;
                request = new Pack_Proto();
                int offset = 0;
                //读取数据
                while (true)
                {
                    int size = recvBytes.Length - offset;

                    if (size <= 0)
                    {
                        //异常
                        server.ErrMsg = "client error: rec to many!!!";
                        return false;
                    }

                    int bytes = 0;
                    try
                    {
                        bytes = socket.Receive(recvBytes, offset, size, SocketFlags.None);//从客户端接受信息,可能会分了多次接收
                    }
                    catch (SocketException e)
                    {
                        //异常
                        server.ErrMsg = $"client error: {e.Message}";
                        return false;
                    }
                    if (bytes == 0)
                    {
                        //超时,或已经断开连接
                        server.ErrMsg = "client error: TimeOut";
                        return false;
                    }
                    //肯定只回复一条,只需要翻译一条
                    if (!request.TryParse(recvBytes, 0, bytes, out int rlen))
                    {
                        //处理失败,数据不够,继续接收
                        offset = bytes;
                    }
                    else
                    {
                        //完成!!!!
                        return true;
                    }
                }
            }

            bool PushReponse(Pack_Proto reponse)
            {
                try
                {
                    socket.Send(reponse.ToBytes());
                }
                catch (Exception e)
                {
                    server.ErrMsg = e.Message;
                    return false;
                }
                return true;
            }

            void OnPoll_client()
            {
                while (true)
                {
                    if(cts.IsCancellationRequested)
                        goto _error;

                    bool ret = PullRequest(out Pack_Proto request);
                    if(!ret)
                        goto _error;

                    ParsePacket(request, out Pack_Proto reponse);

                    
                    ret = PushReponse(reponse);
                    if (!ret)
                        goto _error;
                    
                }
            _error:
                Close();
            }

            public void Close()
            {
                cts.Cancel();
                socket.Close();

                Closed?.Invoke(this);
            }
            public event ClosedHandler Closed;


            void ParsePacket(Pack_Proto request, out Pack_Proto reponse)
            {
                switch (request.func)
                {
                    case 0x03:      // read holding registers
                        Do_03(request, out reponse);
                        break;
                    case 0x01:      // read coils
                        Do_01(request, out reponse);
                        break;
                    case 0x10:      // write multiple registers
                        Do_10(request, out reponse);
                        break;
                    case 0x06:      // Write single register
                        Do_06(request, out reponse);
                        break;
                    case 0x05:      // Write single coil
                        Do_05(request, out reponse);
                        break;
                    case 0x0f:      // Write mutiple coils
                        Do_0f(request, out reponse);
                        break;
                    default:
                        Do_Exception(request, 0x01, out reponse);
                        break;
                }
            }

            #region 处理
            /// <summary>
            /// 异常处理
            /// </summary>
            void Do_Exception(Pack_Proto request, byte e_code, out Pack_Proto reponse)
            {
                byte func = request.func;
                Misc.MyBase.SIGNBIT(ref func, 7);
                reponse = new Pack_Proto()
                {
                    tranid = request.tranid,
                    unitid = request.unitid,
                    func = func,
                    buf = new byte[1] { e_code }
                };
            }
            #region 功能
            /// <summary>
            /// 读多个 COIL
            /// </summary>
            void Do_01(Pack_Proto request, out Pack_Proto reponse)
            {
                int index = 0;
                //开始地址
                UInt16 start = COMMON.ToUInt16_Big_endian(request.buf, index);
                index += 2;
                //数量
                UInt16 num = COMMON.ToUInt16_Big_endian(request.buf, index);
                index += 2;

                if ((num < 0x01) || (num > 0x07D0))
                {
                    Do_Exception(request, 0x03, out reponse);
                    return;
                }
                bool[] value = new bool[num];
                server.GetValue(start, value);
                
                List<byte> data = new List<byte>();
                data.Add((byte)(Math.Ceiling(num / 8.0)));//返回的字节数

                for (int i = 0, j = 0; i < num; i++)
                {
                    if (j == 0)
                        data.Add(0);

                    if (value[i])
                    {
                        data[data.Count - 1] = (byte)(data[data.Count - 1] | Misc.MyBase.BIT(i % 8));
                    }
                    j++;
                    if (j == 8)
                    {
                        j = 0;
                    }
                }

                reponse = new Pack_Proto()
                {
                    tranid = request.tranid,
                    unitid = request.unitid,
                    func = request.func,
                    buf = data.ToArray()
                };
            }


            /// <summary>
            /// 读多个REG
            /// </summary>
            /// <param name="request"></param>
            /// <param name="reponse"></param>
            void Do_03(Pack_Proto request, out Pack_Proto reponse)
            {

                int index = 0;
                //开始地址
                UInt16 start = COMMON.ToUInt16_Big_endian(request.buf, index);
                index += 2;
                //数量
                UInt16 num = COMMON.ToUInt16_Big_endian(request.buf, index);
                index += 2;





                if ((num < 0x01) || (num > 0x007D))
                {
                    Do_Exception(request, 0x03, out reponse);
                    return;
                }


                UInt16[] value = new UInt16[num];
                server.GetValue(start, value);
                

                List<byte> data = new List<byte>();
                data.Add((byte)(num * 2));
                for (int i = 0; i < num; i++)
                {
                    data.AddRange(value[i].GetBytes_Big_endian());
                }

                reponse = new Pack_Proto()
                {
                    tranid = request.tranid,
                    unitid = request.unitid,
                    func = request.func,
                    buf = data.ToArray()
                };
            }

            /// <summary>
            /// 写单个coil
            /// </summary>
            void Do_05(Pack_Proto request, out Pack_Proto reponse)
            {
                int index = 0;
                //开始地址
                UInt16 start = COMMON.ToUInt16_Big_endian(request.buf, index);
                index += 2;
                //数量
                UInt16 value = COMMON.ToUInt16_Big_endian(request.buf, index);
                index += 2;


                if ((value != 0xff00) && (value != 0x0000))
                {
                    Do_Exception(request, 0x03, out reponse);
                    return;
                }



                bool[] v = new bool[1];

                if (value == 0xff00)
                    v[0] = true;
                else
                    v[0] = false;
                
                server.SetValue(start, v);

                reponse = request;
            }
            /// <summary>
            /// 写单个REG
            /// </summary>
            void Do_06(Pack_Proto request, out Pack_Proto reponse)
            {

                int index = 0;
                //开始地址
                UInt16 start = COMMON.ToUInt16_Big_endian(request.buf, index);
                index += 2;
                //数量
                UInt16 value = COMMON.ToUInt16_Big_endian(request.buf, index);
                index += 2;

                UInt16[] v = new UInt16[1];
                v[0] = value;
                server.SetValue(start, v);

                reponse = request;
            }

            /// <summary>
            /// 写多个coil
            /// </summary>
            /// <param name="request"></param>
            /// <param name="reponse"></param>
            void Do_0f(Pack_Proto request, out Pack_Proto reponse)
            {
                int index = 0;
                //开始地址
                UInt16 start = COMMON.ToUInt16_Big_endian(request.buf, index);
                index += 2;
                //数量
                UInt16 num = COMMON.ToUInt16_Big_endian(request.buf, index);
                index += 2;

                if ((num < 0x01) || (num > 0x007D))
                {
                    Do_Exception(request, 0x03, out reponse);
                    return;
                }


                byte byte_cnt = request.buf[index];
                index++;

                bool[] v = new bool[num];
                for (int i = 0, j = 0; i < num; i++)
                {
                    if (Misc.MyBase.CHECKBIT(request.buf[index], j))
                        v[i] = true;
                    else
                        v[i] = false;
                    j++;
                    if (j == 8)
                    {
                        j = 0;
                        index++;
                    }
                }

                server.SetValue(start, v);

                List<byte> data = new List<byte>();
                data.AddRange(start.GetBytes_Big_endian());
                data.AddRange(num.GetBytes_Big_endian());

                reponse = new Pack_Proto()
                {
                    tranid = request.tranid,
                    unitid = request.unitid,
                    func = request.func,
                    buf = data.ToArray()
                };
            }


            /// <summary>
            /// 写多个REG
            /// </summary>
            /// <param name="request"></param>
            /// <param name="reponse"></param>
            void Do_10(Pack_Proto request, out Pack_Proto reponse)
            {
                int index = 0;
                //开始地址
                UInt16 start = COMMON.ToUInt16_Big_endian(request.buf, index);
                index += 2;
                //数量
                UInt16 num = COMMON.ToUInt16_Big_endian(request.buf, index);
                index += 2;
                byte byte_cnt = request.buf[index];
                index++;

                if ((num < 0x01) || (num > 0x007D))
                {
                    Do_Exception(request, 0x03, out reponse);
                    return;
                }



                UInt16[] v = new UInt16[num];
                for (int i = 0; i < num; i++)
                {
                    v[i] = COMMON.ToUInt16_Big_endian(request.buf, index);
                    index += 2;
                }

                server.SetValue(start, v);

                List<byte> data = new List<byte>();
                data.AddRange(start.GetBytes_Big_endian());
                data.AddRange(num.GetBytes_Big_endian());

                reponse = new Pack_Proto()
                {
                    tranid = request.tranid,
                    unitid = request.unitid,
                    func = request.func,
                    buf = data.ToArray()
                };
            }
            #endregion
            #endregion
        }





        public event PropertyChangedEventHandler PropertyChanged;
    }
}