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

namespace GeneralGommunication
{
    /// <summary>
    /// 完全不用, 时间间隔不能确保对，导致经常数据异常。 ASCII 好很多
    /// </summary>
    public class ModbusRtu : IModbusBase
    {
        /// <summary>
        /// 通讯中
        /// </summary>
        public bool IsInComm { get; private set; }

        /// <summary>
        /// 指令成功回复时，回复的耗时
        /// </summary>
        public TimeSpan Elapsed { get; private set; }

        /// <summary>
        /// 10s 内最大回复的耗时
        /// </summary>
        public int MaxElapsedms10s { get; private set; }
        public ErrorType Error { get; private set; }

        public bool IsError => !string.IsNullOrEmpty(ErrMsg);
        public string ErrMsg { get; private set; }

        /// <summary>
        /// 接收的数据
        /// </summary>
        public byte[] RXDs { get; private set; }
        public IGeneralComm serial;

        /// <summary>
        /// 数据包间隔时间，必须确保 接收包 与 发送包之间等待 3.5T以上时间
        /// </summary>
        public int PackIntervalms { get; private set; } = 10;

        public bool IsCommConnected { get; private set; }
        public string CommErrMsg { get; private set; }

        Stopwatch stopwatch_lastRecPack = new Stopwatch();
        TimeSpan maxElapsed;
        Stopwatch stopwatch_maxElapsed = new Stopwatch();
        public ModbusRtu()
        {
        }

        public void Init(IGeneralComm serial)
        {
            this.serial = serial;
            this.serial.DataReceived += Serial_DataReceived;


            Misc.BindingOperations.SetBinding(serial, nameof(serial.IsConnected), this, nameof(IsCommConnected));
            Misc.BindingOperations.SetBinding(serial, nameof(serial.ErrMsg), this, nameof(CommErrMsg));
        }


        #region  功能

        /// <summary>
        /// 读多个 COIL
        /// </summary>
        /// <param name="device_no">设备号</param>
        /// <param name="addr">参数首地址</param>
        /// <param name="cnt">线圈个数</param>
        /// <param name="values">输出线圈状态</param>
        /// <returns></returns>
        public bool Do_01(byte device_no, int addr, int cnt, out IEnumerable<bool> values)
        {
            values = null;
            if (IsInComm)
            {
                Error = ErrorType.Busy;
                ErrMsg = "线路忙";
                return false;
            }

            Pack_Proto p = new Pack_Proto()
            {
                device_no = device_no,
                func = 0x01,
                addr = addr,
                cnt = cnt,
                expRecBytes = (int)Math.Ceiling(cnt / 8.0) + 5
            };

            if (!SendAndWaitForReponse(p))
                return false;

            List<bool> blist = new List<bool>();
            int byte_len = p.sendBuf[2];
            int index = 3;
            for (int i = 0; i < byte_len; i++)
            {
                byte d = p.sendBuf[index];
                for (int j = 0; j < 8; j++)
                {
                    if ((d & (1 << j)) != 0)
                        blist.Add(true);
                    else
                        blist.Add(false);

                    if (blist.Count() >= cnt)
                    {
                        //完成
                        break;
                    }
                }
                index++;
            }

            values = blist;
            return true;
        }

        Pack_Proto currProto;

        public event PropertyChangedEventHandler PropertyChanged;

        private void Serial_DataReceived(IGeneralComm sender, byte[] buf)
        {
            stopwatch_lastRecPack.Restart();

            if (!IsInComm)
            {
                //异常,不应该接收到东西
                return;
            }

            Pack_Proto pack = currProto;
            pack.recBuf.AddRange(buf);

            if (pack.recBuf.Count() >= 3)
            {
                if (pack.recBuf[0] != pack.device_no)
                {
                    pack.error = ErrorType.Unknown;
                    pack.errMsg = $"接收的SLAVE地址{pack.recBuf[0]}与发送的{pack.device_no}不一致";

                    IsInComm = false;
                    pack.cancellationTokenSource.Cancel();
                    return;
                }

                if (pack.recBuf[1] != pack.func)
                {
                    pack.error = ErrorType.Unknown;
                    pack.errMsg = $"错误码: {pack.recBuf[1]} 异常码: {pack.recBuf[2]}";

                    IsInComm = false;
                    pack.cancellationTokenSource.Cancel();
                    return;
                }

                if (pack.recBuf.Count() >= pack.expRecBytes)
                {
                    UInt16 crc = pack.recBuf.CRC16(0, pack.expRecBytes - 2);
                    UInt16 rec_crc = pack.recBuf.ToUInt16_Little_Endian(pack.expRecBytes - 2);
                    if (crc != rec_crc)
                    {
                        pack.error = ErrorType.CRCError;
                        pack.errMsg = $"指令码:{pack.func:X2} CRC 校验出错 接收:{rec_crc:X4} 计算:{crc:X4}";
                    }
                    else
                    {

                        pack.funcData.AddRange(pack.recBuf.Skip(2).Take(pack.expRecBytes - 4));
                        pack.error = ErrorType.OK;
                    }
                    IsInComm = false;
                    pack.cancellationTokenSource.Cancel();
                    return;
                }
            }
        }
        bool SendAndWaitForReponse(Pack_Proto p)
        {
            currProto = p;
            var data = p.sendBuf;
            data.Add(p.device_no);
            data.Add(p.func);
            data.AddRange(p.funcData);
            UInt16 crc = data.CRC16(0, data.Count());
            data.AddRange(crc.GetBytes_Little_Endian());
            p.funcData.Clear();

            while (stopwatch_lastRecPack.IsRunning)
            {
                //上次接收到现在距离不到 3.5T 间隔，不能发送
                //等待
                int delayms = (int)(PackIntervalms - stopwatch_lastRecPack.ElapsedMilliseconds);
                if (delayms > 0)
                    Task.Delay(delayms).Wait();
                else
                    break;
            }
            stopwatch_lastRecPack.Stop();
            serial.Write(p.sendBuf.ToArray());
            if (!serial.IsConnected)
            {
                Error = ErrorType.Disconnected;
                ErrMsg = serial.ErrMsg;
                return false;
            }

            IsInComm = true;
            p.stopwatch.Start();
            try
            {
                int ms = 300;// PackIntervalms * 2;
                Task.Delay(ms, p.cancellationTokenSource.Token).Wait();
            }
            catch (Exception e)
            {
                //被打断了，肯定通讯完成
            }

            p.stopwatch.Stop();
            IsInComm = false;
            RXDs = p.recBuf.ToArray();
            if (p.error == ErrorType.Idle)
            {
                //通讯超时
                Error = ErrorType.TimeOut;
                ErrMsg = "通讯超时";
                return false;
            }
            else
            {
                Error = p.error;
                ErrMsg = p.errMsg;
                if (p.error == ErrorType.OK)
                {
                    Elapsed = p.stopwatch.Elapsed;
                    if (Elapsed > maxElapsed)
                    {
                        maxElapsed = Elapsed;
                    }
                    if (!stopwatch_maxElapsed.IsRunning)
                    {
                        stopwatch_maxElapsed.Start();
                    }
                    else if (stopwatch_maxElapsed.Elapsed > TimeSpan.FromSeconds(10))
                    {
                        stopwatch_maxElapsed.Restart();
                        MaxElapsedms10s = (int)maxElapsed.TotalMilliseconds;
                        maxElapsed = Elapsed;
                    }

                    return true;
                }
                else
                    return false;
            }


        }
        public class Pack_Proto
        {
            public byte device_no;
            public byte func;
            public int addr;
            public int cnt;
            public int expRecBytes;

            public List<byte> sendBuf = new List<byte>();
            public List<byte> recBuf = new List<byte>();
            public List<byte> funcData = new List<byte>();
            public ErrorType error;
            public string errMsg = "";
            public Stopwatch stopwatch = new Stopwatch();
            public System.Threading.CancellationTokenSource cancellationTokenSource = new System.Threading.CancellationTokenSource();
        }


        /// <summary>
        /// 读多个Holding REGs
        /// </summary>
        /// <param name="addr"></param>
        /// <param name="cnt"></param>
        /// <param name="values"></param>
        /// <returns></returns>
        public bool Do_03(byte device_no, int addr, int cnt, out List<UInt16> values)
        {
            values = null;
            if (IsInComm)
            {
                Error = ErrorType.Busy;
                ErrMsg = "线路忙";
                return false;
            }
            Pack_Proto p = new Pack_Proto()
            {
                device_no = device_no,
                func = 0x03,
                addr = addr,
                cnt = cnt,
                expRecBytes = cnt * 2 + 5
            };
            p.funcData.AddRange(((UInt16)addr).GetBytes_Big_endian());
            p.funcData.AddRange(((UInt16)cnt).GetBytes_Big_endian());
            if (!SendAndWaitForReponse(p))
                return false;

            {
                List<UInt16> blist = new List<UInt16>();
                int index = 1;

                while (index < p.funcData.Count())
                {
                    blist.Add(p.funcData.ToUInt16_Big_Endian(index));
                    index += 2;
                }
                values = blist;
            }
            return true;
        }

        /// <summary>
        /// Write Single Coil
        /// </summary>
        /// <param name="addr"></param>
        /// <param name="data"></param>
        /// <returns></returns>
        public bool Do_05(byte device_no, int addr, bool dat)
        {
            if (IsInComm)
            {
                Error = ErrorType.Busy;
                ErrMsg = "线路忙";
                return false;
            }

            Pack_Proto p = new Pack_Proto()
            {
                device_no = device_no,
                addr = addr,
                func = 0x05,
                expRecBytes = 6
            };
            p.funcData.AddRange(((UInt16)addr).GetBytes_Big_endian());
            UInt16 d = (dat) ? (UInt16)0xff00 : (UInt16)0;
            p.funcData.AddRange(d.GetBytes_Big_endian());
            return SendAndWaitForReponse(p);
        }


        /// <summary>
        /// Write Single REG
        /// </summary>
        /// <param name="addr"></param>
        /// <param name="data"></param>
        /// <returns></returns>
        public bool Do_06(byte device_no, int addr, UInt16 dat)
        {
            if (IsInComm)
            {
                Error = ErrorType.Busy;
                ErrMsg = "线路忙";
                return false;
            }

            Pack_Proto p = new Pack_Proto()
            {
                device_no = device_no,
                addr = addr,
                func = 0x06,
                expRecBytes = 8
            };
            p.funcData.AddRange(((UInt16)addr).GetBytes_Big_endian());
            p.funcData.AddRange(((UInt16)dat).GetBytes_Big_endian());
            return SendAndWaitForReponse(p);

        }
        /// <summary>
        /// 写多个coil
        /// </summary>
        /// <param name="addr"></param>
        /// <param name="datas"></param>
        /// <returns></returns>
        public bool Do_0F(byte device_no, int addr, List<bool> datas)
        {
            if (IsInComm)
            {
                Error = ErrorType.Busy;
                ErrMsg = "线路忙";
                return false;
            }

            Pack_Proto p = new Pack_Proto()
            {
                device_no = device_no,
                addr = addr,
                func = 0x0F,
                expRecBytes = 6
            };
            p.funcData.AddRange(((UInt16)p.addr).GetBytes_Big_endian());
            p.funcData.AddRange(((UInt16)datas.Count()).GetBytes_Big_endian());

            p.funcData.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))
                {
                    b |= (byte)(1 << i);
                }
                j++;
                if (j == 8)
                {
                    p.funcData.Add(b);
                    b = 0;
                    j = 0;
                }
            }
            if (j != 0)
                p.funcData.Add(b);

            return SendAndWaitForReponse(p);
        }


        /// <summary>
        /// 写多个REG
        /// </summary>
        /// <param name="addr"></param>
        /// <param name="datas"></param>
        /// <returns></returns>
        public bool Do_10(byte device_no, int addr, IEnumerable<UInt16> datas)
        {
            if (IsInComm)
            {
                Error = ErrorType.Busy;
                ErrMsg = "线路忙";
                return false;
            }

            Pack_Proto p = new Pack_Proto()
            {
                device_no = device_no,
                addr = addr,
                func = 0x10,
                expRecBytes = 8
            };
            p.funcData.AddRange(((UInt16)p.addr).GetBytes_Big_endian());
            p.funcData.AddRange(((UInt16)datas.Count()).GetBytes_Big_endian());
            p.funcData.Add((byte)(datas.Count() * 2));

            for (int i = 0; i < datas.Count(); i++)
            {
                p.funcData.AddRange(datas.ElementAt(i).GetBytes_Big_endian());
            }
            return SendAndWaitForReponse(p);
        }
        #endregion
    }
}
