using Newtonsoft.Json.Linq;
using NLog;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Threading;
using static GeneralGommunication.Modbus_Transaction;

namespace GeneralGommunication
{
    /// <summary>
    /// 异步modbus RTU
    /// </summary>
    public class ModbusRtuAsync:INotifyPropertyChanged
    {
        public Logger logger = NLog.LogManager.GetCurrentClassLogger();

        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// 包出错次数
        /// </summary>
        public int ErrCnt { get; protected set; }

        
        /// <summary>
        /// 通讯速度测量模块
        /// </summary>
        public CommSpeedMeasuring Csm => csm;

        /// <summary>
        /// 有数据需要发送
        /// </summary>
        public event SendDataEventHandler SendMsgEvent;

        /// <summary>
        /// 对于全部 有返回的函数调用，都使用Dispatcher,使线程同步
        /// </summary>
        public Dispatcher Dispatcher;


        List<byte> currPack = new List<byte>();


        /// <summary>
        /// 指令队列，必须等上1条指令回复了，才能发下条指令
        /// </summary>
        List<Modbus_Transaction> Transactions;

        /// <summary>
        /// 当前正在等待回复的指令
        /// </summary>
        Modbus_Transaction currTran;

        CommSpeedMeasuring csm = new CommSpeedMeasuring();

        /// <summary>
        /// currTran 重发次数
        /// </summary>
        int retryCnt = 0;
        List<int> deviceConnected = new List<int>();
        
        public event DeviceConnectEventHander DeviceConnectEvent;

        /// <summary>
        /// 通讯中
        /// </summary>
        public bool IsBusy
        {
            get {
                if (currTran != null)
                    return true;
                if (Transactions.Count() > 0)
                    return true;
                return false;

            }
        }

        System.Timers.Timer timer3d5t;//通讯包的 3.5T 时间间隔。 实际只是 15ms定时而已
        System.Timers.Timer timerTimeOut;//等待回复超时
        public ModbusRtuAsync() 
        {
            Transactions = new List<Modbus_Transaction>();

            timer3d5t = new System.Timers.Timer();
            timer3d5t.Interval = 30;//设得再小也没有用。 它只是用系统的15ms定时器触发而已。 
            timer3d5t.AutoReset = false;//触发一次
            timer3d5t.Elapsed += Timer3d5t_Elapsed;

            timerTimeOut = new System.Timers.Timer();
            timerTimeOut.Interval = 500;
            timerTimeOut.AutoReset = false;
            timerTimeOut.Elapsed += TimerTimeOut_Elapsed;
        }


        public bool IsConnected(byte deviceNo) 
        {
            if (deviceConnected.Contains(deviceNo))
                return true;
            return false;
        }


        private void Timer3d5t_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            ParsePack();
        }

        
        
        private void TimerTimeOut_Elapsed(object sender, System.Timers.ElapsedEventArgs e) 
        {
            if (currTran == null)//没有数据需要发送,异常
                return;
            
            //大于1秒也没回复，异常
            //重试2次
            retryCnt++;
            logger.Error($"TIMEOUT 第{retryCnt}次超时");
            if (retryCnt >= 2)
            {
                //已经发送了2次，放弃
                if (deviceConnected.Contains(currTran.deviceNo))
                { 
                    //之前连接是好的。
                    deviceConnected.Remove(currTran.deviceNo);

                    //触发通知
                    DeviceConnectEvent?.Invoke(this, new DeviceConnectEventArgs()
                    {
                        deviceNo = currTran.deviceNo,
                        isConnected = false
                    });
                }

                //删除发送给这个设备的全部指令
                Transactions.RemoveAll(t => t.deviceNo == currTran.deviceNo);
                currTran = null;

                retryCnt = 0;
                if (Transactions.Count() != 0) //还要其它需要发送
                {
                    byte[] msg = GetSendMsg();
                    SendMsgEvent?.Invoke(this,msg);
                }
                return;
            }
            else
            {
                //重新把指令放回队列
                Transactions.Insert(0, currTran);
                currTran = null;

                //再发一次指令
                byte[] msg = GetSendMsg();
                SendMsgEvent?.Invoke(this, msg);
            }
        }

        public void RecMsg(byte[] recBuf)
        {
            //停止超时检查
            timerTimeOut.Stop();
            
            //15ms后再处理。
            timer3d5t.Stop();

            csm.IncRec(recBuf.Count());
            currPack.AddRange(recBuf);
            if (logger.IsDebugEnabled) {
                string msg = recBuf.ToString_Bytes2Hex();
                logger.Debug($"ACK {msg}");
            }

            timer3d5t.Start();
        }

        /// <summary>
        /// 获取 发送队列 第1条msg
        /// </summary>
        /// <returns></returns>
        byte[] GetSendMsg()
        {
            //TODO 要处理  TimeOut / ParseFuncPack / GetSendMsg 线性同步问题

            if (currTran == null)
            {
                //当前没有指令正在发送

                if (Transactions.Count() == 0)//队列没有需要发送的指令
                    return null;


                currTran = Transactions.First();
                
                Transactions.RemoveAt(0);
            }
            else
            {
                //发送出去中，等待回复
                return null;          
            }


            var tran = currTran;

            //调试: 打印发送信息
            if(logger.IsDebugEnabled)
            {
                string msg = tran.sendBuf.ToString_Bytes2Hex();
                if (tran.desription == null)
                    logger.Debug($"REQ {msg}");
                else
                    logger.Debug($"REQ {tran.desription} {msg}");
            }
            //开始计时
            timerTimeOut.Start();

            return tran.sendBuf.ToArray();
        }

        /// <summary>
        /// 复位全部状态，通常由于通讯模块检测到连接断开导致的
        /// </summary>
        public void ResetMsg()
        {
            Transactions.Clear();
            currTran = null;
            retryCnt = 0;
            timer3d5t.Stop();
            timerTimeOut.Stop();
            csm.Reset();

            //触发通知
            var isConneced = deviceConnected.ToArray();
            deviceConnected.Clear();

            foreach (var deviceNo in isConneced)
            {
                //触发通知
                DeviceConnectEvent?.Invoke(this, new DeviceConnectEventArgs()
                {
                    deviceNo = deviceNo,
                    isConnected = false
                });
            }
        }

        /// <summary>
        /// 已经收完一份数据，不会再有更多，包解析
        /// </summary>
        void ParsePack()
        {
            if (currTran == null)
            {
                //没有请求。。。
                currPack.Clear();
                return;
            }

            if (currPack.Count() < 3)
            {
                //异常 数据太少 通知重发 
                logger.Error($"ACK 接收数据小于3个byte");
                ErrCnt++;
                currPack.Clear();
                TimerTimeOut_Elapsed(null, null);
                return;
            }

            if (currPack[0] != currTran.deviceNo)
            {
                logger.Error($"ACK 接收的SLAVE地址{currPack[0]}与发送的{currTran.deviceNo}不一致");
                ErrCnt++;
                currPack.Clear();
                TimerTimeOut_Elapsed(null, null);
                return;
            }

            if (currPack[1] != currTran.func)
            {
                logger.Error($"ACK 错误码:{currPack[1]:X2} 异常码:{currPack[2]:X2}");
                ErrCnt++;
                currPack.Clear();
                TimerTimeOut_Elapsed(null, null);
                return;
            }

            if (currPack.Count() < currTran.expRecBytes)
            {
                logger.Error($"ACK 数据还没接收完");
                ErrCnt++;
                currPack.Clear();
                TimerTimeOut_Elapsed(null, null);
                return;
            }


            UInt16 crc = currPack.CRC16(0, currTran.expRecBytes - 2);
            UInt16 rec_crc = currPack.ToUInt16_Little_Endian(currTran.expRecBytes - 2);
            if (crc != rec_crc)
            {
                logger.Error($"ACK 指令码:{currTran.func:X2} CRC 校验出错 接收:{rec_crc:X4} 计算:{crc:X4}");
                //DeviceConnectEvent?.Invoke(this, new DeviceConnectEventArgs() { deviceNo = currTran.deviceNo, isConnected = false, errMsg = errMsg });
                ErrCnt++;
                currPack.Clear();
                TimerTimeOut_Elapsed(null, null);
                return;
            }

            //成功解析出一个包
            currTran.funcData.AddRange(currPack.Skip(2).Take(currTran.expRecBytes - 4));
            csm.IncPack(1);
            currPack.Clear();

            //触发连接通知
            if (!deviceConnected.Contains(currTran.deviceNo))
            {
                deviceConnected.Add(currTran.deviceNo);
                DeviceConnectEvent?.Invoke(this, new DeviceConnectEventArgs() { deviceNo = currTran.deviceNo, isConnected = true });
            }

            switch (currTran.func)
            {
                case 0x03:
                    {
                        List<UInt16> values = new List<UInt16>();
                        int index = 1;

                        while (index < currTran.funcData.Count())
                        {
                            values.Add(currTran.funcData.ToUInt16_Big_Endian(index));
                            index += 2;
                        }

                        currTran.retData = currTran.parse16FuncPack(values);
                    }
                    break;
            }

            if (logger.IsDebugEnabled)
            {
                if (currTran.retData == null)
                    logger.Debug($"ACK");
                else
                    logger.Debug($"ACK {Newtonsoft.Json.JsonConvert.SerializeObject(currTran.retData)}");
            }

            //有很多指令是没有回复数据的， 回调只是通知 指令已经执行了而已
            //调用回调
            if (currTran.asyncDelegate != null)
            {
                if (Dispatcher != null)//线程同步执行
                {
                    Dispatcher.BeginInvoke(currTran.asyncDelegate, currTran.asyncContext, currTran.retData);
                }
                else
                {
                    currTran.asyncDelegate.Invoke(currTran.asyncContext, currTran.retData);
                }
            }

            //空出当前交易位置
            currTran = null;

            if (Transactions.Count() > 0)
            {
                //队列还有需要发送的指令,通知外部获取指令发送
                byte[] msg = GetSendMsg();
                SendMsgEvent?.Invoke(this,msg);
            }
        }

        public void AddTran(Modbus_Transaction tran)
        {
            //放入 交易队列
            Transactions.Add(tran);
            if (currTran == null)
            {
                //当前没有指令正在发送
                byte[] msg = GetSendMsg();
                SendMsgEvent?.Invoke(this,msg);
            }
        }

        /// <summary>
        /// 读多个Holding REGs
        /// </summary>
        /// <param name="deviceNo"></param>
        /// <param name="addr"></param>
        /// <param name="cnt"></param>
        /// <param name="desription"></param>
        /// <param name="parseU16FuncPack"></param>
        /// <param name="asyncDelegate"></param>
        /// <param name="asyncContext"></param>
        /// <returns></returns>
        public Modbus_Transaction Do_03(byte deviceNo, int addr, int cnt,
            string desription,
           ParseU16FuncPackHandler parseU16FuncPack,
           CallBackHandler asyncDelegate, object asyncContext) 
        {
            Modbus_Transaction tran = new Modbus_Transaction()
            {
                deviceNo = deviceNo,
                func = 0x03,
                addr = addr,
                cnt = cnt,
                expRecBytes = cnt * 2 + 5,
                desription = desription,
                parse16FuncPack = parseU16FuncPack,
                asyncDelegate = asyncDelegate,
                asyncContext = asyncContext
            };
            tran.funcData.AddRange(((UInt16)addr).GetBytes_Big_endian());
            tran.funcData.AddRange(((UInt16)cnt).GetBytes_Big_endian());

            ToSendBuf(tran);
            
            return tran;
        }
        void ToSendBuf(Modbus_Transaction tran)
        {
            var data = tran.sendBuf;
            data.Add((byte)tran.deviceNo);
            data.Add(tran.func);
            data.AddRange(tran.funcData);
            UInt16 crc = data.CRC16(0, data.Count());
            data.AddRange(crc.GetBytes_Little_Endian());
            tran.funcData.Clear();
        }
        /// <summary>
        /// 写单个 Holding REG
        /// </summary>
        /// <param name="deviceNo"></param>
        /// <param name="addr"></param>
        /// <param name="cnt"></param>
        /// <param name="desription"></param>
        /// <param name="asyncDelegate"></param>
        /// <param name="asyncContext"></param>
        /// <returns></returns>
        public Modbus_Transaction Do_06(int deviceNo, int addr, UInt16 value,
            string desription,
           CallBackHandler asyncDelegate, object asyncContext)
        {
            Modbus_Transaction tran = new Modbus_Transaction()
            {
                deviceNo = deviceNo,
                func = 0x06,
                addr = addr,
                expRecBytes = 8,
                desription = desription,
                asyncDelegate = asyncDelegate,
                asyncContext = asyncContext
            };
            tran.funcData.AddRange(((UInt16)addr).GetBytes_Big_endian());
            tran.funcData.AddRange(((UInt16)value).GetBytes_Big_endian());
            ToSendBuf(tran);
            return tran;
        }

        /// <summary>
        /// 写多个 Holding REG
        /// </summary>
        /// <param name="deviceNo"></param>
        /// <param name="addr"></param>
        /// <param name="cnt"></param>
        /// <param name="desription"></param>
        /// <param name="asyncDelegate"></param>
        /// <param name="asyncContext"></param>
        /// <returns></returns>
        public Modbus_Transaction Do_10(int deviceNo, int addr, IEnumerable<UInt16> datas,
           string desription,
           CallBackHandler asyncDelegate, object asyncContext)
        {
            Modbus_Transaction tran = new Modbus_Transaction()
            {
                deviceNo = deviceNo,
                func = 0x10,
                addr = addr,
                expRecBytes = 8,
                desription = desription,
                asyncDelegate = asyncDelegate,
                asyncContext = asyncContext
            };
            tran.funcData.AddRange(((UInt16)tran.addr).GetBytes_Big_endian());
            tran.funcData.AddRange(((UInt16)datas.Count()).GetBytes_Big_endian());
            tran.funcData.Add((byte)(datas.Count() * 2));

            for (int i = 0; i < datas.Count(); i++)
            {
                tran.funcData.AddRange(datas.ElementAt(i).GetBytes_Big_endian());
            }

            ToSendBuf(tran);
            return tran;
        }
    }


}
