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

namespace GeneralGommunication
{
    /// <summary>
    /// 独立线程运行。 固需要做线程同步。
    /// 支持Push, Call
    /// 其中Call, 一“问”，必须一“答”。 长时间没答，重发“问”，连续3次都没 "答" 触发通讯断开。
    /// 正常模式，多个"问" 会储存在缓存区， 等上一条 "答" ，才能发下一条"问"。
    /// 打包模式, 多个"问" 没有先后关系，把它们打包, 不等 "答" 就同时发送"问"。  但 还是必须等 全部 "答" 都回复，才能执行一个动作。
    /// </summary>
    public abstract class Dev7E:IDev7E
    {
        public Logger logger = NLog.LogManager.GetCurrentClassLogger();

        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// 包出错次数
        /// </summary>
        public int ErrCnt { get; protected set; }
        /// <summary>
        /// 连接成功；
        /// 当命令多次发送失败，IsConnected = false
        /// </summary>
        public bool IsConnected { get; private set; }

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

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

        public event TimeOutEventHandler TimeOutEvent;




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

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

        /// <summary>
        /// 当前正在等待回复的指令
        /// </summary>
        COMMREQ_TransactionBase currTran;
        /// <summary>
        /// currTran 发送后，开始计时
        /// </summary>
        Stopwatch stopwatch_timeOut;
        /// <summary>
        /// currTran 重发次数
        /// </summary>
        int retryCnt = 0;

        CommSpeedMeasuring csm = new CommSpeedMeasuring();
        /// <summary>
        /// 指令前序 序号
        /// </summary>
        protected int PrefixIndex { get; set; } = 1;

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

        DispatcherTimer timerTimeOut;//等待回复超时

        public Dev7E()
        {
            Transactions = new List<COMMREQ_TransactionBase>();

            stopwatch_timeOut = new Stopwatch();

            timerTimeOut = new DispatcherTimer();
            timerTimeOut.Interval = TimeSpan.FromSeconds(0.5);
            timerTimeOut.Tick += TimerTimeOut_Tick;
            timerTimeOut.Start();
        }


        #region 测量

        #endregion


        private void TimerTimeOut_Tick(object sender, EventArgs e)
        {
            //TODO 要处理  TimeOut / ParseFuncPack / GetSendMsg 线性同步问题
            if (!IsConnected)
                return;

            if (currTran == null)
                return;//没有指令

            if (!stopwatch_timeOut.IsRunning)
                return;//还没开始发送

            if (stopwatch_timeOut.Elapsed < TimeSpan.FromSeconds(1))
                return;//发送到现在，还没到1秒，继续等

            //大于1秒也没回复，异常
            //重试3次
            retryCnt++;
            stopwatch_timeOut.Stop();//停止，等下次发送

            if (retryCnt >= 3)
            {
                //已经重试了3次，放弃
                ResetMsg();
                //连接断开
                TimeOutEvent?.Invoke(this);
                return;
            }
            else
            {
                //再发一次指令
                byte[] msg = GetSendMsg();
                SendMsgEvent?.Invoke(this,msg);
            }
        }

        public void RecMsg(byte[] recBuf)
        {
            //IsConnected = true;
            csm.IncRec(recBuf.Count());

            List<byte[]> packs = new List<byte[]>();
            lock (currPack)
            {
                for (int i = 0; i < recBuf.Count(); i++)
                {
                    if (recBuf[i] == 0x7e)
                    {
                        //找到头了
                        //结束之前的包
                        if (currPack.Count > 0)
                        {
                            var pack = currPack.ToArray();
                            packs.Add(pack);
                            currPack.Clear();
                            csm.IncPack(1);
                        }
                    }
                    currPack.Add(recBuf[i]);
                }
            }

            for (int i = 0; i < packs.Count(); i++) {
                ParsePack(packs[i]);
            }
        }


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

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

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


                currTran = Transactions.First();
                retryCnt = 0;
                Transactions.RemoveAt(0);
            }
            else
            {
                //发送出去中，等待回复
                if (stopwatch_timeOut.IsRunning)
                    return null;//已经发送了，计时器都启动了                
            }

            if (currTran is COMMREQ_Transaction)
            {
                var tran = currTran as COMMREQ_Transaction;

                //找出 COMMREQ
                var commReq = tran.commReq;
                List<byte> pack = new List<byte>();

                //添加前序
                pack.AddRange(commReq.Prefix);

                //添加数据
                if (tran.datas != null)
                    pack.AddRange(tran.datas);

                //调试: 打印发送信息
                if (tran.datasObj == null)
                    logger.Debug($"REQ {commReq.PrefixString}");
                else
                    logger.Debug($"REQ {commReq.PrefixString} {Newtonsoft.Json.JsonConvert.SerializeObject(tran.datasObj)}");

                //转为 7E格式 
                var buf = GetSendPack(pack);

                //开始计时
                stopwatch_timeOut.Restart();

                return buf.ToArray();
            }
            else
            {
                //集群交易
                List<byte> bufs = new List<byte>();

                var _tran = currTran as COMMREQ_TransactionMulti;

                //获取还没接收到数据的交易，不能没有交易
                var trans = _tran.transactions.FindAll(t => !t.hasRet);
                if (trans.Count() == 0)
                    throw new Exception("没有 [没接收到数据的交易]，程序写错");

                foreach (var tran in trans) 
                { 
                    //找出 COMMREQ
                    var commReq = tran.commReq;

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

                    //添加前序
                    pack.AddRange(commReq.Prefix);

                    //添加数据
                    if (tran.datas != null)
                        pack.AddRange(tran.datas);

                    //转为 7E格式 
                    var buf = GetSendPack(pack);

                    bufs.AddRange(buf);

                    //调试: 打印发送信息
                    if (tran.datasObj == null)
                        logger.Debug($"REQ multi {commReq.PrefixString}");
                    else
                        logger.Debug($"REQ multi {commReq.PrefixString} {Newtonsoft.Json.JsonConvert.SerializeObject(tran.datasObj)}");
                }


                //开始计时
                stopwatch_timeOut.Restart();

                return bufs.ToArray();
            }
        }

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

        /// 包解析
        /// </summary>
        void ParsePack(byte[] pack)
        {
            //第0个肯定是7E
            if (pack.Count() == 1)
            {
                //只有一个 7E
                return;
            }

            //转义数据
            if (!Protocol7ECommon.Pdu2Data(pack, out List<byte> datas))
            {
                //异常
                return;
            }
            //解析后的 datas 没有了 帧包装 7E

            pack = datas.ToArray();

            if (pack.Count() == 1)
            {
                //只有一个字节，异常！！！
                ErrCnt++;
                return;
            }

            //解析后的 datas 没有了 帧包装 7E
            byte crc8 = Misc.CRC.CRC8(pack, 0, pack.Count() - 1);
            if (pack.Last() != crc8)
            {
                //CRC8 出错
                ErrCnt++;
                return;
            }
            
            IsConnected = true;

            ParsePackAfterCheckCRC8(pack);
        }
        protected abstract void ParsePackAfterCheckCRC8(byte[] buf);

        /// <summary>
        /// 功能包解析
        /// </summary>
        /// <param name="datas"></param>
        protected virtual void ParseFuncPack(byte[] pack)
        {
            if (currTran == null)
            {
                //没有请求。。。
                return;
            }

            if (currTran is COMMREQ_Transaction)
            {
                ParseFuncPack_Single(pack);
            }
            else
            {
                ParseFuncPack_Multi(pack);
            }
        }

        protected bool ParseFuncPack_Single(byte[] pack) 
        {
            object retData = null;
            var tran = currTran as COMMREQ_Transaction;
            var commReq = tran.commReq;

            //还有B0
            if (commReq.ResponseTotalLen + PrefixIndex > pack.Count())
            {
                //失败，指令长度不对！！
                logger.Error($"ACK expect:{commReq.PrefixString} len={commReq.ResponseTotalLen + 1}, but reponse len ={pack.Count()} reponse: {pack.ToString_Bytes2HexAndAscii()}");
                ErrCnt++;
                return false;
            }

            if (commReq.IsMatch != null)
            {
                if (!commReq.IsMatch(commReq, pack, PrefixIndex, commReq.IsMatchContext, ref retData))
                {
                    //回复对不上请求
                    logger.Error($"ACK expect:{commReq.PrefixString} len={commReq.ResponseTotalLen + 1}, but reponse len ={pack.Count()} reponse: {pack.ToString_Bytes2HexAndAscii()}");
                    ErrCnt++;
                    return false;
                }
            }
            else
            {
                if (!IsMatch(commReq, pack))
                {
                    //回复对不上请求
                    logger.Error($"ACK expect:{commReq.PrefixString} len={commReq.ResponseTotalLen + 1}, but reponse len ={pack.Count()} reponse: {pack.ToString_Bytes2HexAndAscii()}");
                    ErrCnt++;
                    return false;
                }

                //处理指令
                //还有B0
                if (commReq.ParseFuncPack != null)//解析回复数据
                    retData = commReq.ParseFuncPack(pack, commReq.Prefix.Count() + PrefixIndex);
            }
            tran.hasRet = true;
            tran.retData = retData;

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

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

            //停止超时检测
            stopwatch_timeOut.Stop();

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

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

            return true;
        }

        bool IsMatch2(COMMREQ commReq, byte[] pack, ref object retData) 
        {
            //还有B0
            if (commReq.ResponseTotalLen + PrefixIndex > pack.Count())
            {
                //失败，指令长度不对！！
                //logger.Error($"ACK expect:{commReq.PrefixString} len={commReq.ReponseTotalLen + 1}, but reponse len ={pack.Count()} reponse: {bytes2hex(pack)}");
                //ErrCnt++;
                return false;
            }

            if (commReq.IsMatch != null)
            {
                //自定义IsMatch规则
                if (!commReq.IsMatch(commReq, pack, PrefixIndex, commReq.IsMatchContext, ref retData))
                {
                    //回复对不上请求
                    //logger.Error($"ACK expect:{commReq.PrefixString} ,but reponse: {bytes2hex(pack)}");
                    return false;
                }
            }
            else
            {
                //通用IsMatch规则
                if (!IsMatch(commReq, pack))
                {
                    //回复对不上请求
                    //logger.Error($"ACK expect:{commReq.PrefixString} ,but reponse: {bytes2hex(pack)}");
                    return false;
                }

                //处理指令
                //还有B0
                if (commReq.ParseFuncPack != null)//解析回复数据
                    retData = commReq.ParseFuncPack(pack, commReq.Prefix.Count() + PrefixIndex);
            }
            return true;
        }

        protected bool ParseFuncPack_Multi(byte[] pack)
        {
            object retData = null;
            var _tran = currTran as COMMREQ_TransactionMulti;

            //寻找合适的commReq
            var trans = _tran.transactions.FindAll(t => !t.hasRet);
            COMMREQ commReq = null;
            COMMREQ_Transaction tran = null;

            foreach (var t in trans)
            {
                tran = t;
                commReq = tran.commReq;
                if (IsMatch2(commReq, pack, ref retData))//匹配正确，就是这个tran的回复
                    break;
                commReq = null;
                tran = null;
            }

            if (commReq == null) {
                //不能解析
                logger.Error($"ACK multi reponse len ={pack.Count()} reponse: {pack.ToString_Bytes2HexAndAscii()}");
                ErrCnt++;
                return false;
            }

            tran.hasRet = true;
            tran.retData = retData;
            //解析成功
            if (retData == null)
                logger.Debug($"ACK multi {commReq.PrefixString}");
            else
                logger.Debug($"ACK multi {commReq.PrefixString} {Newtonsoft.Json.JsonConvert.SerializeObject(retData)}");

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

            if (_tran.transactions.All(t => t.hasRet))
            {
                //完成, 全部回复
                if (_tran.asyncDelegate != null)
                {
                    var retDatas = _tran.transactions.Select(t => t.retData).ToArray();
                    if (dispatcher != null)//线程同步执行
                    {
                        dispatcher.BeginInvoke(_tran.asyncDelegate, _tran.asyncContext, retDatas);
                    }
                    else
                    {
                        _tran.asyncDelegate.Invoke(_tran.asyncContext, retDatas);
                    }
                }

                //停止超时检测
                stopwatch_timeOut.Stop();

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

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

                return true;

            }

            return false;
        }

        /// <summary>
        /// 与指令前序比较，一定要完全一样
        /// </summary>
        /// <param name="commreq"></param>
        /// <param name="datas"></param>
        /// <returns></returns>
        protected bool IsMatch(COMMREQ commreq, byte[] datas)
        {
            int prefix_index = PrefixIndex;
            //datas[0] 是B0, 不应该用于判断

            //与指令前序一个个比较
            for (int i = 0; i < commreq.Prefix.Length; i++)
            {
                if (commreq.Prefix[i] != datas[prefix_index + i])
                {
                    return false;
                }
            }

            return true;
        }


        /// <summary>
        /// 数据发送，输入不需要包含开头的7E； 输出会添加CRC8 和 7E
        /// </summary>
        /// <param name="datas"></param>
        protected virtual List<byte> GetSendPack(List<byte> datas)
        {
            //需要在后面添加CRC8
            byte crc8 = Misc.CRC.CRC8(datas, 0, datas.Count());
            datas.Add(crc8);

            Protocol7ECommon.Data2Pdu(datas, out List<byte> pack);
            pack.Add(0x7E);//一定要加0x7E
            return pack;
        }
        protected void AddTran(COMMREQ_Transaction tran)
        {
            if (transactionMulti!=null)
            {
                transactionMulti.transactions.Add(tran);
            }
            else
            {
                //放入 交易队列
                Transactions.Add(tran);
                if (currTran == null)
                {
                    //当前没有指令正在发送
                    byte[] msg = GetSendMsg();
                    SendMsgEvent?.Invoke(this,msg);
                }
            }
        }
        COMMREQ_TransactionMulti transactionMulti;
        void BuildMultiTransAsync() 
        {
            if (transactionMulti!=null) {
                throw new Exception("已经是 BuildMultiTrans, 不能再次执行, 必须调用 AddMultiTrans");
            }
            transactionMulti = new COMMREQ_TransactionMulti();
        }

        public void BuildMultiTrans(Action action)
        {
            BuildMultiTrans(action, null, null);
        }
        public void BuildMultiTrans(Action action, CallBackHandler asyncDelegate, object asyncContext)
        {
            BuildMultiTransAsync();

            action();

            AddMultiTransAsync(asyncDelegate, asyncContext);
        }

        void AddMultiTransAsync(CallBackHandler asyncDelegate, object asyncContext) 
        {
            if (transactionMulti.transactions.Count() == 0) {
                throw new Exception("MultiTrans 为空");
            }
            transactionMulti.asyncDelegate = asyncDelegate;
            transactionMulti.asyncContext = asyncContext;

            //放入 交易队列
            Transactions.Add(transactionMulti);
            transactionMulti = null;

            if (currTran == null)
            {
                //当前没有指令正在发送
                byte[] msg = GetSendMsg();

                SendMsgEvent?.Invoke(this, msg);
            }
        }
    }


}
