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.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 SendMsgEventHander SendMsgEvent;



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


        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;

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

            stopwatch_timeOut = new Stopwatch();
        }

        #region 测量

        #endregion


        /// <summary>
        /// 发送指令的超时判断
        /// </summary>
        public void OnPoll_TimeOut()
        {
            //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次，放弃
                IsConnected = false;
                currTran = null;

                //清空 指令队列
                Transactions.Clear();
                return;
            }
            else
            {
                //再发一次指令
                SendMsgEvent?.Invoke(this);
            }
        }

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

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


            //OnPoll_TimeOut();
        }


        /// <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)}");
                //byte[] prefix = COMMREQ.ToPrefix("RCD", 0);
                //if (prefix.SequenceEqual(commReq.Prefix)) 
                //{

                //}

                //转为 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;
            }

            byte crc8 = Misc.CRC.CRC8(pack, 0, pack.Count() - 1);
            if (pack.Last() != crc8)
            {
                //CRC8 出错
                ErrCnt++;
                return;
            }
            ParsePackAfterCheckCRC8(pack);
        }
        protected abstract void ParsePackAfterCheckCRC8(byte[] buf);

        string bytes2hex(byte[] pack)
        {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < pack.Count(); i++)
            {
                sb.Append($"{pack[i]:X2} ");
            }
            return sb.ToString();
        }
        /// <summary>
        /// 功能包解析
        /// </summary>
        /// <param name="datas"></param>
        protected 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.ReponseTotalLen + 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)
            {
                if (!commReq.IsMatch(commReq, pack, PrefixIndex, commReq.IsMatchContext, ref retData))
                {
                    //回复对不上请求
                    logger.Error($"ACK expect:{commReq.PrefixString} len={commReq.ReponseTotalLen + 1}, but reponse len ={pack.Count()} reponse: {bytes2hex(pack)}");
                    ErrCnt++;
                    return false;
                }
            }
            else
            {
                if (!IsMatch(commReq, pack))
                {
                    //回复对不上请求
                    logger.Error($"ACK expect:{commReq.PrefixString} len={commReq.ReponseTotalLen + 1}, but reponse len ={pack.Count()} reponse: {bytes2hex(pack)}");
                    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)
            {
                //队列还有需要发送的指令,通知外部获取指令发送
                SendMsgEvent?.Invoke(this);
            }

            return true;
        }

        bool IsMatch2(COMMREQ commReq, byte[] pack, ref object retData)
        {
            //还有B0
            if (commReq.ReponseTotalLen + 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: {bytes2hex(pack)}");
                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)
                {
                    if (Dispatcher != null)//线程同步执行
                    {
                        Dispatcher.BeginInvoke(_tran.asyncDelegate, _tran.asyncContext, _tran);
                    }
                    else
                    {
                        tran.asyncDelegate.Invoke(_tran.asyncContext, _tran);
                    }
                }

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

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

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

                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)
        {
            //datas.Insert(0, 0x80);//插入B0

            //需要在后面添加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)
                {
                    //当前没有指令正在发送
                    SendMsgEvent?.Invoke(this);
                }
            }
        }
        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)
            {
                //当前没有指令正在发送
                SendMsgEvent?.Invoke(this);
            }
        }
    }

    /// <summary>
    /// 测量通讯速度模块
    /// </summary>
    public class CommSpeedMeasuring : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// 通讯速度 测量中
        /// </summary>
        public bool IsMeasuring { get; private set; }

        /// <summary>
        ///  通讯速度 byte/s
        /// </summary>
        public double CommSpeed { get; private set; }

        /// <summary>
        /// 通讯速度 单位 pack/s
        /// </summary>
        public double PackSpeed { get; private set; }


        int packCnt = 0;
        int recCnt = 0;
        Stopwatch stopwatch = new Stopwatch();
        protected CancellationTokenSource cancellationTokenSource;

        public void StartMeasure()
        {
            if (IsMeasuring)
                return;
            IsMeasuring = true;
            Reset();
            cancellationTokenSource = new CancellationTokenSource();
            //启动线程，测量速度
            Task.Factory.StartNew(MeasureTask, cancellationTokenSource.Token);
        }
        public void StopMeasure()
        {
            cancellationTokenSource.Cancel();
            //停止线程
            IsMeasuring = false;
        }
        void MeasureTask()
        {
            stopwatch.Start();
            while (!cancellationTokenSource.IsCancellationRequested)
            {
                if (stopwatch.Elapsed > TimeSpan.FromSeconds(1))
                {
                    int rcnt, pcnt;
                    double totalsec;
                    lock (this)
                    {
                        totalsec = stopwatch.Elapsed.TotalSeconds;
                        stopwatch.Restart();
                        rcnt = recCnt;
                        pcnt = packCnt;
                        recCnt = 0;
                        packCnt = 0;
                    }

                    //1秒均值
                    CommSpeed = rcnt / totalsec;
                    PackSpeed = pcnt / totalsec;
                }
                Task.Delay(100).Wait();
            }
            stopwatch.Stop();
        }

        /// <summary>
        /// 异常时调用
        /// </summary>
        public void Reset()
        {
            lock (this)
            {
                recCnt = 0;
                packCnt = 0;
            }
        }

        /// <summary>
        /// 需要在 RecMsg(byte[] recBuf) 中调用
        /// </summary>
        /// <param name="cnt"></param>
        public void IncRec(int cnt)
        {
            if (!IsMeasuring)
                return;
            lock (this)
            {
                recCnt += cnt;
            }
        }

        /// <summary>
        /// 需要在 ParsePack(pack); 后调用
        /// </summary>
        /// <param name="cnt"></param>
        public void IncPack(int cnt)
        {
            if (!IsMeasuring)
                return;
            lock (this)
            {
                packCnt += cnt;
            }
        }
    }
}
