using NLog;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Windows.Threading;
using static GeneralGommunication.Modbus_Transaction;

namespace GeneralGommunication
{
    /// <summary>
    /// <para>异步modbus ASCII</para>
    /// 3个线程会调用它: <para/>
    /// 1.接收数据线程调用 RecMsg, func解码后，需要转为Dispatcher的线程触发func回复通知；
    /// func解码后，也会触发SendMsgEvent事件；<para/>
    /// 2.外部调用func，触发SendMsgEvent事件；<para/>
    /// 3.超时定时器，触发SendMsgEvent事件；<para/>
    /// </summary>
    public class ModbusAsciiAsync : IModbusAsciiAsync
    {
        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;


        public event DeviceConnectEventHander DeviceConnectEvent;




        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>();

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

        DispatcherTimer timerTimeOut;//等待回复超时
        public ModbusAsciiAsync()
        {
            Transactions = new List<Modbus_Transaction>();

            timerTimeOut = new DispatcherTimer();
            timerTimeOut.Interval = TimeSpan.FromSeconds(0.2);
            timerTimeOut.Tick += TimerTimeOut_Tick;
            dispatcher = timerTimeOut.Dispatcher;
        }

        /// <summary>
        /// 通讯中
        /// </summary>
        public bool IsBusy()
        {

            lock (Transactions)
            {
                if (currTran != null)
                    return true;
                if (Transactions.Count() > 0)
                    return true;
                return false;
            }

        }

        /// <summary>
        /// 检查发生列表是否包含某条指令
        /// </summary>
        /// <param name="deviceNo"></param>
        /// <param name="desription"></param>
        /// <returns></returns>
        public bool Contain(int deviceNo, string desription)
        {
            lock (Transactions)
            {
                if (currTran != null)
                {
                    if (currTran.deviceNo == deviceNo && currTran.desription == desription)
                    {
                        return true;
                    }
                }

                if (Transactions.Count() > 0)
                {
                    return Transactions.Any(t => t.deviceNo == deviceNo && t.desription == desription);
                }
                else
                {
                    return false;
                }
            }
        }

        private void TimerTimeOut_Tick(object sender, EventArgs e)
        {
            timerTimeOut.Stop();

            RetrySendMsg();
        }

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

        void RetrySendMsg()
        {
            bool isNeedToSendMsg = false;
            bool isDisconnected = false;
            int deviceNo = 0;

            lock (Transactions)
            {
                if (currTran == null)//没有数据需要发送
                    return;

                //大于1秒也没回复，异常
                //重试2次
                retryCnt++;
                if (retryCnt >= 3)
                {
                    //已经发送了2次，放弃
                    AckErrNoRec($"第{retryCnt}次重试失败 放弃");

                    if (deviceConnected.Contains(currTran.deviceNo))
                    {
                        //之前连接是好的。
                        deviceConnected.Remove(currTran.deviceNo);

                        isDisconnected = true;
                        deviceNo = currTran.deviceNo;
                    }

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

                    currTran = null;
                    retryCnt = 0;

                    if (Transactions.Count() != 0) //还要其它需要发送
                    {
                        isNeedToSendMsg = true;
                    }
                }
                else
                {
                    AckErrNoRec($"第{retryCnt + 1}次重试");
                    //重新把指令放回队列
                    Transactions.Insert(0, currTran);
                    currTran = null;

                    //再发一次指令
                    isNeedToSendMsg = true;
                }
            }

            if (isDisconnected) {
                TriggerDeviceConnectEvent(new DeviceConnectEventArgs(){ deviceNo = deviceNo, isConnected = false, errMsg = "无回复"});
            }

            if (isNeedToSendMsg)
            {
                var buf = GetSendMsg();

                TriggerSendMsgEvent(buf);
            }
        }
        
        void TriggerDeviceConnectEvent(DeviceConnectEventArgs e) {
            dispatcher.BeginInvoke(DeviceConnectEvent, this, e);
        }

        void TriggerSendMsgEvent(byte[] data) 
        {
            dispatcher.BeginInvoke(SendMsgEvent, this, data);
        }

        void TriggerCurrTranAsyncDelegate() 
        {
            //线程同步执行
            dispatcher.BeginInvoke(currTran.asyncDelegate, currTran.asyncContext, currTran.retData);
        }
        
        
        public void RecMsg(byte[] recBuf)
        {
            //停止超时检查
            timerTimeOut.Stop();

            csm.IncRec(recBuf.Count());

            bool isFinished = false;
            bool isError = false;

            lock (Transactions)
            {
                if (currTran == null)
                {
                    //没有请求。。。
                    return;
                }

                if (currTran.EnableLog)
                {
                    AckDebug_Rec(recBuf);
                }


                //这次需要判断的位置
                int index = currPack.Count();
                currPack.AddRange(recBuf);
                
                for (int i = 0; i < recBuf.Count(); i++)
                {
                    if (ParsePack(index + i, out isError))
                    {
                        //处理完，删除全部接收
                        currPack.Clear();
                        isFinished = true;
                        break;
                    }
                }
            }

            if (isFinished)
            {
                if (isError)
                {
                    //异常，需要重发
                    RetrySendMsg();
                }
                else
                {
                    //成功解析出包
                    retryCnt = 0; //复位重发次数
                    
                    ParsePack2();
                }

                return;
            }
            else
            {
                //还没接收完！！！！
                timerTimeOut.Start();
            }
        }

        /// <summary>
        /// 获取 发送队列 第1条msg
        /// </summary>
        /// <returns></returns>
        byte[] GetSendMsg()
        {
            timerTimeOut.Stop();

            //TODO 要处理  TimeOut / ParseFuncPack / GetSendMsg 线性同步问题
            lock (Transactions)
            {
                if (currTran == null)
                {
                    //当前没有指令正在发送

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


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

                //调试: 打印发送信息
                if (currTran.EnableLog)
                {
                    ReqDebug();
                }

                //开始计时
                timerTimeOut.Start();

                return currTran.sendBuf.ToArray();
            }
        }


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

            retryCnt = 0;
            timerTimeOut.Stop();
            csm.Reset();

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

            foreach (var deviceNo in isConneced)
            {
                //触发通知
                TriggerDeviceConnectEvent(new DeviceConnectEventArgs(){deviceNo = deviceNo,isConnected = false,errMsg= "链路断开" });
            }
        }

        void ReqDebug() 
        {
            if (logger.IsDebugEnabled)
            {
                string msg = currTran.sendBuf.ToString_Bytes2Hex();
                string msg_ascii = currTran.sendBuf.ToString_Bytes2Ascii();
                
                logger.Debug($"REQ {currTran.deviceNo:00} {currTran.desription} | {msg}| {msg_ascii}");
            }
        }
        void AckDebug_Rec(IEnumerable<byte> recBuf)
        {
            if (logger.IsDebugEnabled)
            {
                string msg = recBuf.ToString_Bytes2Hex();
                string msg_ascii = recBuf.ToString_Bytes2Ascii();
                logger.Debug($"rec {currTran.deviceNo:00} {currTran.desription} | {msg}| {msg_ascii}");
            }
            ErrCnt++;
        }
        void AckErrNoRec(string errMsg)
        {
            if (logger.IsErrorEnabled)
            {
                logger.Error($"ACK {currTran.deviceNo:00} {currTran.desription} {errMsg}");
            }
            ErrCnt++;
        }
        void AckErr(string errMsg) 
        {
            if (logger.IsErrorEnabled)
            {
                string msg = currPack.ToString_Bytes2Hex();
                string msg_ascii = currPack.ToString_Bytes2Ascii();
                logger.Error($"ACK {currTran.deviceNo:00} {currTran.desription} {errMsg} | {msg}| {msg_ascii}");
            }
            ErrCnt++;
        }
        void AckDebug(string debugMsg)
        {
            if (logger.IsDebugEnabled)
            {
                string msg = currTran.funcData.ToString_Bytes2Hex();
                logger.Debug($"ACK {currTran.deviceNo:00} {currTran.desription} {debugMsg} | {msg}");
            }
            ErrCnt++;
        }

        /// <summary>
        /// <para>每个接收byte解析, 处理完，返回true;  </para>
        /// <para>还需要更多byte，返回false</para>
        /// </summary>
        /// <param name="index">当前处理的currPack[index]</param>
        bool ParsePack(int index, out bool isError)
        {
            isError = false;
            if (index == 0)
            {
                if (currPack[index] != (byte)':')
                {
                    //包头错，可以删除
                    AckErr("包头错");
                    isError = true;
                    return true;
                }
            }
            else 
            {
                //匹配 0x0d，0x0a
                if (currPack[index] == 0x0a)
                {
                    if (currPack[index - 1] != 0x0d)
                    {
                        //结尾出错, 可以删除
                        AckErr("包尾错");
                        isError = true;
                        return true;
                    }

                    //: \r\n 已经判断完
                    int len = index + 1;
                    if (len < 10) 
                    {
                        AckErr("字节数太少");
                        isError = true;
                        return true;
                    }

                    byte? addr = currPack.Ascii2Hex(1);
                    if (addr == null)
                    {
                        AckErr("地址错");
                        isError = true;
                        return true;
                    }
                    if (addr != currTran.deviceNo)
                    {
                        //不是回复这里的
                        //AckErr("地址错");
                        isError = true;
                        return true;
                    }

                    //可以解析了
                    //转为hex
                    byte[] recBuf = currPack.Ascii2Hex(1, len - 3);  //:,\d,\a 3个不要的字符
                    if (recBuf == null)
                    {
                        AckErr($"不能翻译为 ASCII");
                        isError = true;
                        return true;
                    }
                    byte lrc = recBuf.LRC8(0, recBuf.Count() - 1);
                    byte rec_lrc = recBuf[recBuf.Count() - 1];

                    if (lrc != rec_lrc)
                    {
                        AckErr($"LRC 校验出错 接收:{rec_lrc:X4} 计算:{lrc:X2}");
                        isError = true;
                        return true;
                    }

                    if (recBuf[1] != currTran.func)
                    {
                        AckErr($"错误码:{currPack[1]} 异常码:{currPack[2]}");
                        isError = true;
                        return true;
                    }

                    //if (len != currTran.expRecBytes)
                    //{
                    //    AckErr("字节数异常 期待{currTran.expRecBytes}Bytes 实际收到{len}");
                    //    return true;
                    //}


                    //成功解析出一个包
                    currTran.funcData.AddRange(recBuf.Skip(2).Take(recBuf.Count() - 3));//不要 devNo, func, lrc 这 3个byte
                    csm.IncPack(1);
                    return true;
                }
                else if (currPack[index - 1] == 0x0d) 
                {
                    AckErr("包尾错");
                    isError = true;
                    return true;
                }
            }
            return false;
        }

        void ParsePack2()
        {

            //触发连接通知
            int deviceNo;
            lock (Transactions) {
                deviceNo = currTran.deviceNo;
            }

            if (!deviceConnected.Contains(deviceNo))
            {
                deviceConnected.Add(deviceNo);
                TriggerDeviceConnectEvent(new DeviceConnectEventArgs() { deviceNo = deviceNo, isConnected = true });
            }

            lock (Transactions)
            {
                switch (currTran.func)
                {
                    case 0x03:
                    case 0x04:
                        {
                            List<UInt16> values = new List<UInt16>();
                            //index=0 为字节数
                            int index = 1;

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

                            currTran.retData = currTran.parse16FuncPack(values);

                            if (currTran.EnableLog)
                            {
                                if (currTran.retData != null)
                                {
                                    string msg = $"{Newtonsoft.Json.JsonConvert.SerializeObject(currTran.retData)}";
                                    AckDebug(msg);
                                }
                            }
                        }
                        break;
                    case 0x06:

                        if (currTran.EnableLog)
                        {
                            AckDebug("OK");
                        }

                        break;
                    case 0x10:

                        if (currTran.EnableLog)
                        {
                            AckDebug("OK");
                        }

                        break;
                    default:
                        //不受支持的功能
                        {
                            currTran.retData = currTran.funcData;

                            if (currTran.EnableLog)
                            {
                                AckDebug("OK");
                            }
                        }
                        break;
                }
            }

            //有很多指令是没有回复数据的， 回调只是通知 指令已经执行了而已
            //调用回调
            if (currTran.asyncDelegate != null)
            {
                TriggerCurrTranAsyncDelegate();
            }

            bool isNeedToSendMsg = false;
            lock (Transactions)
            {
                //空出当前交易位置
                currTran = null;

                if (Transactions.Count() > 0)
                {
                    //队列还有需要发送的指令,通知外部获取指令发送
                    isNeedToSendMsg = true;
                    
                }
            }

            if (isNeedToSendMsg) {
                var buf = GetSendMsg();
                TriggerSendMsgEvent(buf);
            }
        }
        public void AddTran(Modbus_Transaction tran)
        {
            bool isNeedToSendMsg = false;

            //放入 交易队列
            lock (Transactions)
            {
                Transactions.Add(tran);
                if (currTran == null)
                {
                    //当前没有指令正在发送
                    isNeedToSendMsg = true;
                }
            }

            if (isNeedToSendMsg)
            {

                var buf = GetSendMsg();
                TriggerSendMsgEvent(buf);
            }
        }

        /// <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(int deviceNo, int addr, int cnt,
            string desription,
           ParseU16FuncPackHandler parseU16FuncPack,
           CallBackHandler asyncDelegate, object asyncContext)
        {
            //发送
            //: (1byte)
            //devNo (1 ascii = 2 byte)
            //func (1 ascii = 2 byte)
            //addr (2 ascii = 4 byte)
            //regCnt (2 ascii = 4 byte)
            //lrc (1 ascii = 2 byte)
            //0x0d,0x0a (2 byte)

            //接收
            //: (1 byte)
            //devNo (1 ascii = 2 byte)
            //func (1 ascii = 2 byte)
            //byteCnt (1 ascii = 2 byte)
            //reg*byteCnt/2 (2 * byteCnt/2 ascii = 4*N byte)
            //lrc (1 ascii = 2 byte)
            //0x0d,0x0a (2 byte)

            int expRecBytes = (cnt * 2 + 4) * 2 + 3;

            Modbus_Transaction tran = new Modbus_Transaction()
            {
                deviceNo = deviceNo,
                func = 0x03,
                addr = addr,
                cnt = cnt,
                expRecBytes = expRecBytes,
                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;
        }

        /// <summary>
        /// 读多个Input 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_04(int deviceNo, int addr, int cnt,
            string desription,
           ParseU16FuncPackHandler parseU16FuncPack,
           CallBackHandler asyncDelegate, object asyncContext)
        {
            //发送
            //: (1byte)
            //devNo (1 ascii = 2 byte)
            //func (1 ascii = 2 byte)
            //addr (2 ascii = 4 byte)
            //regCnt (2 ascii = 4 byte)
            //lrc (1 ascii = 2 byte)
            //0x0d,0x0a (2 byte)

            //接收
            //: (1 byte)
            //devNo (1 ascii = 2 byte)
            //func (1 ascii = 2 byte)
            //byteCnt (1 ascii = 2 byte)
            //reg*byteCnt/2 (2 * byteCnt/2 ascii = 4*N byte)
            //lrc (1 ascii = 2 byte)
            //0x0d,0x0a (2 byte)

            int expRecBytes = (cnt * 2 + 4) * 2 + 3;

            Modbus_Transaction tran = new Modbus_Transaction()
            {
                deviceNo = deviceNo,
                func = 0x04,
                addr = addr,
                cnt = cnt,
                expRecBytes = expRecBytes,
                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;
        }


        public void ToSendBuf(Modbus_Transaction tran)
        {
            var data = new List<byte>();
            data.Add((byte)tran.deviceNo);
            data.Add(tran.func);
            data.AddRange(tran.funcData);

            byte lrc = data.LRC8(0, data.Count());
            data.Add(lrc);

            tran.funcData.Clear();

            //ASCII TO HEX
            byte[] recBuf = data.HexToAscii();

            tran.sendBuf.Add((byte)':');
            tran.sendBuf.AddRange(recBuf);
            tran.sendBuf.Add(0x0d);
            tran.sendBuf.Add(0x0a);
        }
        /// <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)
        {
            //发送
            //: (1byte)
            //devNo (1 ascii = 2 byte)
            //func (1 ascii = 2 byte)
            //addr (2 ascii = 4 byte)
            //reg (2 ascii = 4 byte)
            //lrc (1 ascii = 2 byte)
            //0x0d,0x0a (2 byte)

            //接收
            //: (1 byte)
            //devNo (1 ascii = 2 byte)
            //func (1 ascii = 2 byte)
            //addr (2 ascii = 4 byte)
            //reg (2 ascii = 4 byte)
            //lrc (1 ascii = 2 byte)
            //0x0d,0x0a (2 byte)

            int expRecBytes = (7 * 2) + 3;


            Modbus_Transaction tran = new Modbus_Transaction()
            {
                deviceNo = deviceNo,
                func = 0x06,
                addr = addr,
                expRecBytes = expRecBytes,
                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)
        {
            //发送
            //: (1byte)
            //devNo (1 ascii = 2 byte)
            //func (1 ascii = 2 byte)
            //addr (2 ascii = 4 byte)
            //regCnt (2 ascii = 4 byte)
            //reg*regCnt (2*regCnt ascii = 2*regCnt*2 byte)
            //lrc (1 ascii = 2 byte)
            //0x0d,0x0a (2 byte)

            //接收
            //: (1 byte)
            //devNo (1 ascii = 2 byte)
            //func (1 ascii = 2 byte)
            //addr (2 ascii = 4 byte)
            //regCnt (2 ascii = 4 byte)
            //lrc (1 ascii = 2 byte)
            //0x0d,0x0a (2 byte)

            int expRecBytes = (7 * 2) + 3;

            Modbus_Transaction tran = new Modbus_Transaction()
            {
                deviceNo = deviceNo,
                func = 0x10,
                addr = addr,
                expRecBytes = expRecBytes,
                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;
        }
    }


}
