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

namespace FLY.Modbus.WithThread
{
    /// <summary>
    /// 基于线程,所有动作都会堵塞,必须确保只有一个Task操作
    /// </summary>
    public class ClientTCP:INotifyPropertyChanged,IModbusClient
    {
        /// <summary>
        /// 通信超时, 默认是 1s
        /// </summary>
        public TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds(6);
        /// <summary>
        /// socket 错误信息
        /// </summary>
        public string ErrMsg { get; set; }
        
        /// <summary>
        /// 连接成功
        /// </summary>
        public bool IsConnected { get; set; }

        /// <summary>
        /// 标识,modbus tcp的参数
        /// </summary>
        public byte UnitID = 1;
        
        UInt16 tranid = 12;
        UInt16 GetFreeTranID()
        {
            tranid++;
            return tranid;
        }

        /// <summary>
        /// 远端地址
        /// </summary>
        public IPEndPoint RemoteEP { get; set; }
        Socket sock;

        public event PropertyChangedEventHandler PropertyChanged;

        public ClientTCP(IPEndPoint ep)
        {
            RemoteEP = ep;
        }

        /// <summary>
        /// 建立连接
        /// </summary>
        public bool Connect()
        {
            lock (this)
            {
                if (IsConnected)
                    return true;

                sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                try
                {
                    sock.Connect(RemoteEP);
                    IsConnected = true;
                    return true;
                }
                catch (Exception e)
                {
                    ErrMsg = e.Message;
                    return false;
                }
            }
        }
        /// <summary>
        /// 停止
        /// </summary>
        public void Close()
        {
            lock (this)
            {
                if (!IsConnected)
                    return;
                sock.Close();
                IsConnected = false;
            }
        }

        //会堵塞,必须使用一个线程执行
        bool TranExecute(Pack_Proto request, out Pack_Proto reponse)
        {
            reponse = new Pack_Proto();

            if (!IsConnected)
                return false;

            lock (this)
            {
                if (!IsConnected)
                    return false;
                bool isError = false;
                try
                {
                    sock.Send(request.ToBytes());
                }
                catch (Exception e)
                {
                    ErrMsg = e.Message;
                    isError = true;
                    goto _end;
                }

                byte[] recvBytes = new byte[sock.ReceiveBufferSize];//回复的指令不可能大于2K
                sock.ReceiveTimeout = (int)Timeout.TotalMilliseconds;
                int offset = 0;
                //读取数据
                while (true)
                {
                    
                    int size = recvBytes.Length - offset;

                    if (size <= 0)
                    {
                        //异常
                        ErrMsg = "TimeOut";
                        isError = true;
                        goto _end;
                    }

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


        /// <summary>
        /// 读多个 COIL
        /// </summary>
        /// <param name="addr"></param>
        /// <param name="cnt"></param>
        /// <param name="values"></param>
        /// <returns></returns>
        public bool Do_01(int addr, int cnt, out IEnumerable<bool> values)
        {
            values = null;
            List<byte> data = new List<byte>();
            data.AddRange(((UInt16)addr).GetBytes_Big_endian());
            data.AddRange(((UInt16)cnt).GetBytes_Big_endian());


            Pack_Proto request = new Pack_Proto()
            {
                tranid = GetFreeTranID(),
                unitid = UnitID,
                func = 0x01,
                buf = data.ToArray()
            };
            if (!TranExecute(request, out Pack_Proto reponse))
                return false;


            if (reponse.func != 0x01)
            {
                //有问题!!!触发异常事件
                ErrMsg = "返回信息 reponse.func != 0x01";
                return false;
            }

            List<bool> blist = new List<bool>();
            int byte_len = reponse.buf[0];
            int index = 1;
            for (int i = 0; i < byte_len; i++)
            {
                for (int j = 0; j < 8; j++)
                {
                    if (Misc.MyBase.CHECKBIT(reponse.buf[index], j))
                        blist.Add(true);
                    else
                        blist.Add(false);
                    if (blist.Count() >= cnt)
                    {
                        //完成
                        i = byte_len;
                        break;
                    }
                }
                index++;
            }

            values = blist;
            return true;
        }
        /// <summary>
        /// 读多个REG
        /// </summary>
        /// <param name="addr"></param>
        /// <param name="cnt"></param>
        /// <param name="values"></param>
        /// <returns></returns>
        public bool Do_03(int addr, int cnt, out IEnumerable<ushort> values)
        {
            values = null;
            List<byte> data = new List<byte>();
            data.AddRange(((UInt16)addr).GetBytes_Big_endian());
            data.AddRange(((UInt16)cnt).GetBytes_Big_endian());

            Pack_Proto request = new Pack_Proto()
            {
                tranid = GetFreeTranID(),
                unitid = UnitID,
                func = 0x03,
                buf = data.ToArray()
            };

            if (!TranExecute(request, out Pack_Proto reponse))
                return false;

            if (reponse.func != 0x03)
            {
                //有问题!!!触发异常事件
                ErrMsg = "返回信息 reponse.func != 0x03";
                return false;
            }
            List<UInt16> blist = new List<UInt16>();
            int index = 1;

            while (index < reponse.buf.Count())
            {
                blist.Add(COMMON.ToUInt16_Big_endian(reponse.buf, index));
                index += 2;
            }
            values = blist;
            return true;
        }

        /// <summary>
        /// Write Single Coil
        /// </summary>
        /// <param name="addr"></param>
        /// <param name="dat"></param>
        /// <returns></returns>
        public bool Do_05(int addr, bool dat)
        {
            List<byte> data = new List<byte>();
            data.AddRange(((UInt16)addr).GetBytes_Big_endian());
            if (dat)
                data.Add(0xff);
            else
                data.Add(0);
            data.Add(0);

            Pack_Proto request = new Pack_Proto()
            {
                tranid = GetFreeTranID(),
                unitid = UnitID,
                func = 0x05,
                buf = data.ToArray()
            };

            if (!TranExecute(request, out Pack_Proto reponse))
                return false;

            if (reponse.func != 0x05)
            {
                //有问题!!!触发异常事件
                ErrMsg = "返回信息 reponse.func != 0x05";
                return false;
            }
            return true;
        }

        /// <summary>
        /// 写多个coil
        /// </summary>
        /// <param name="addr"></param>
        /// <param name="datas"></param>
        /// <returns></returns>
        public bool Do_0F(int addr, IEnumerable<bool> datas)
        {
            List<byte> data = new List<byte>();
            data.AddRange(((UInt16)addr).GetBytes_Big_endian());
            data.AddRange(((UInt16)datas.Count()).GetBytes_Big_endian());
            data.Add((byte)(Math.Ceiling(datas.Count() / 8.0)));

            byte b = 0;
            int j = 0;
            for (int i = 0; i < datas.Count(); i++)
            {
                if (datas.ElementAt(i))
                    Misc.MyBase.SIGNBIT(ref b, j);
                j++;
                if (j == 8)
                {
                    data.Add(b);
                    b = 0;
                    j = 0;
                }
            }
            if (j != 0)
                data.Add(b);

            Pack_Proto request = new Pack_Proto()
            {
                tranid = GetFreeTranID(),
                unitid = UnitID,
                func = 0x0F,
                buf = data.ToArray()
            };

            if (!TranExecute(request, out Pack_Proto reponse))
                return false;

            if (reponse.func != 0x0F)
            {
                //有问题!!!触发异常事件
                ErrMsg = "返回信息 reponse.func != 0x0F";
                return false;
            }
            return true;
        }

        public bool Do_10(int addr, IEnumerable<ushort> datas)
        {
            List<byte> data = new List<byte>();
            data.AddRange(((UInt16)addr).GetBytes_Big_endian());
            data.AddRange(((UInt16)datas.Count()).GetBytes_Big_endian());
            data.Add((byte)(datas.Count() * 2));
            for (int i = 0; i < datas.Count(); i++)
            {
                data.AddRange(datas.ElementAt(i).GetBytes_Big_endian());
            }

            Pack_Proto request = new Pack_Proto()
            {
                tranid = GetFreeTranID(),
                unitid = UnitID,
                func = 0x10,
                buf = data.ToArray()
            };

            if (!TranExecute(request, out Pack_Proto reponse))
                return false;

            if (reponse.func != 0x10)
            {
                //有问题!!!触发异常事件
                ErrMsg = "返回信息 reponse.func != 0x10";
                return false;
            }
            return true;
        }
    }
}