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

namespace GeneralGommunication
{
    public class ModbusAscii : 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 DateTime LastCommTime { 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 ModbusAscii()
        {
        }

        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;
            }


            int expRecBytes = (int)Math.Ceiling(cnt / 8.0) + 5;
            expRecBytes -= 1;// lrc 比 crc 少一位
            expRecBytes = expRecBytes * 2 + 3; // : ,(ascii 2bytes), 0x0d,0xa

            Pack_Proto p = new Pack_Proto()
            {
                device_no = device_no,
                func = 0x01,
                addr = addr,
                cnt = cnt,
                expRecBytes = expRecBytes
            };

            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;
            int index = pack.recBuf.Count();
            pack.recBuf.AddRange(buf);
            for (int i = 0; i < buf.Count(); i++)
            {
                if (Modbus_Rec(index + i))
                {
                    //处理完，删除全部接收
                    pack.recBuf.Clear();
                    return;
                }
            }

        }

        bool Modbus_Rec(int index)
        {
            Pack_Proto pack = currProto;

            var buf = pack.recBuf;
            if (index == 0)
            {
                if (buf[index] != ':')
                {
                    //包头错，可以删除

                    pack.error = ErrorType.Unknown;
                    pack.errMsg = $"指令码:{pack.func:X2} 包头错";

                    //呼醒 等待！！
                    pack.cancellationTokenSource.Cancel();

                    return true;
                }
            }
            else if (index == 2)
            {
                byte? addr = Ascii2Hex(buf, 1);
                if (addr == null)
                {
                    //异常，无法识别
                    pack.error = ErrorType.Unknown;
                    pack.errMsg = $"指令码:{pack.func:X2} 地址错";

                    //呼醒 等待！！
                    pack.cancellationTokenSource.Cancel();

                    return true;
                }
                if (addr != pack.device_no)
                {
                    //地址错，可以删除

                    pack.error = ErrorType.Unknown;
                    pack.errMsg = $"指令码:{pack.func:X2} 地址错";

                    //呼醒 等待！！
                    pack.cancellationTokenSource.Cancel();

                    return true;
                }
            }
            else if (index == pack.expRecBytes - 1)
            {
                if (buf[index - 1] != 0x0d || buf[index] != 0x0a)
                {
                    //结尾出错, 可以删除

                    pack.error = ErrorType.Unknown;
                    pack.errMsg = $"指令码:{pack.func:X2} 包尾错";

                    //呼醒 等待！！
                    pack.cancellationTokenSource.Cancel();
                    return true;
                }

                //可以解析了
                //转为hex

                byte[] recBuf = Ascii2Hex(pack.recBuf, 1, pack.expRecBytes - 3);
                pack.recBuf.Clear();
                pack.recBuf.AddRange(recBuf);

                byte lrc = recBuf.LRC8(0, recBuf.Count() - 1);
                byte rec_lrc = recBuf[recBuf.Count() - 1];

                if (lrc != rec_lrc)
                {
                    pack.error = ErrorType.CRCError;
                    pack.errMsg = $"指令码:{pack.func:X2} LRC 校验出错 接收:{rec_lrc:X4} 计算:{lrc:X2}";
                }
                else
                {
                    pack.funcData.AddRange(pack.recBuf.Skip(2).Take(recBuf.Count() - 3));
                    pack.error = ErrorType.OK;
                }
                //呼醒 等待！！
                pack.cancellationTokenSource.Cancel();
                return true;
            }
            return false;
        }

        byte? Ascii2Hex(IEnumerable<byte> ascii, int index)
        {
            string msg = System.Text.Encoding.ASCII.GetString(new byte[] { ascii.ElementAt(index), ascii.ElementAt(index + 1) });
            try
            {
                return (byte)int.Parse(msg, System.Globalization.NumberStyles.HexNumber);
            }
            catch
            {
                return null;
            }



        }
        byte[] Ascii2Hex(IEnumerable<byte> ascii, int index, int len)
        {
            if (len % 2 != 0)
                return null;

            List<byte> hex = new List<byte>();
            for (int i = 0; i < len / 2; i++)
            {
                string msg = System.Text.Encoding.ASCII.GetString(new byte[] { ascii.ElementAt(index + i * 2), ascii.ElementAt(index + i * 2 + 1) });
                byte d = (byte)int.Parse(msg, System.Globalization.NumberStyles.HexNumber);
                hex.Add(d);
            }
            return hex.ToArray();

        }
        byte[] HexToAscii(IEnumerable<byte> hex)
        {
            return HexToAscii(hex, 0, hex.Count());
        }
        byte[] HexToAscii(IEnumerable<byte> hex, int index, int len)
        {
            string msg = "";
            for (int i = 0; i < len; i++)
            {
                msg += hex.ElementAt(index + i).ToString("X2");
            }
            return System.Text.Encoding.ASCII.GetBytes(msg);
        }
        bool SendAndWaitForReponse(Pack_Proto p)
        {
            currProto = p;
            var data = new List<byte>();
            data.Add(p.device_no);
            data.Add(p.func);
            data.AddRange(p.funcData);
            byte lrc = data.LRC8(0, data.Count());
            data.Add(lrc);

            //转为 ascii
            p.sendBuf.Add((byte)':');
            p.sendBuf.AddRange(HexToAscii(data));
            p.sendBuf.Add(0x0d);
            p.sendBuf.Add(0x0a);
            ;

            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;
                    LastCommTime = DateTime.Now;
                    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;
            }



            int expRecBytes = cnt * 2 + 5;
            expRecBytes -= 1;// lrc 比 crc 少一位
            expRecBytes = expRecBytes * 2 + 3; // : ,(ascii 2bytes), 0x0d,0xa

            Pack_Proto p = new Pack_Proto()
            {
                device_no = device_no,
                func = 0x03,
                addr = addr,
                cnt = cnt,
                expRecBytes = expRecBytes
            };
            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;
            }
            int expRecBytes = 6;
            expRecBytes -= 1;// lrc 比 crc 少一位
            expRecBytes = expRecBytes * 2 + 3; // : ,(ascii 2bytes), 0x0d,0xa
            Pack_Proto p = new Pack_Proto()
            {
                device_no = device_no,
                addr = addr,
                func = 0x05,
                expRecBytes = expRecBytes
            };
            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;
            }

            int expRecBytes = 8;
            expRecBytes -= 1;// lrc 比 crc 少一位
            expRecBytes = expRecBytes * 2 + 3; // : ,(ascii 2bytes), 0x0d,0xa
            Pack_Proto p = new Pack_Proto()
            {
                device_no = device_no,
                addr = addr,
                func = 0x06,
                expRecBytes = expRecBytes
            };
            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;
            }

            int expRecBytes = 6;
            expRecBytes -= 1;// lrc 比 crc 少一位
            expRecBytes = expRecBytes * 2 + 3; // : ,(ascii 2bytes), 0x0d,0xa
            Pack_Proto p = new Pack_Proto()
            {
                device_no = device_no,
                addr = addr,
                func = 0x0F,
                expRecBytes = expRecBytes
            };
            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;
            }

            int expRecBytes = 8;
            expRecBytes -= 1;// lrc 比 crc 少一位
            expRecBytes = expRecBytes * 2 + 3; // : ,(ascii 2bytes), 0x0d,0xa

            Pack_Proto p = new Pack_Proto()
            {
                device_no = device_no,
                addr = addr,
                func = 0x10,
                expRecBytes = expRecBytes
            };
            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
    }
}
