using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using Misc;
using FLY.Thick.Base.IService;
using FLY.Thick.Blowing.IService;
using FObjBase;
using System.IO;
using Newtonsoft.Json;
using AutoMapper;

namespace FLY.Thick.Blowing.Server
{
    /// <summary>
    /// <para>1.每次收到限位信号(有->没 and 没->有),都会触发 FilmInfoChangedEvent ;时间为上一次限位信号</para>
    /// <para>2.改变辊周长,膜长,都会触发 FilmInfoChangedEvent ;时间为前3次限位信号</para>
    /// <para>3.每次查找FilmInfo, 时间比 当前的限位信号+RenZiJiaPeriod 还要未来,修正 RenZiJiaPeriod,</para>
    /// <para>RenZiJiaPeriod预留10s的余量,避免不断修正</para>
    /// <para>并且触发FilmInfoChangedEvent 时间为上一次限位信号</para>
    /// </summary>
    public class BlowingDetect : IBlowingDetectService, INotifyPropertyChanged
    {
        #region MARKNO
        const int MARKNO_DELAY0 = 1;
        const int MARKNO_DELAY1 = 2;
        #endregion

        #region 输入口定义
        /// <summary>
        /// 限位0 对应输入位
        /// </summary>
        const int Limit0_bit = 9;



        /// <summary>
        /// 限位1 对应输入位
        /// </summary>
        const int Limit1_bit = 10;



        /// <summary>
        /// 辊信号 对应输入位
        /// </summary>
        const int Roll_bit = 11;
        #endregion



        #region 参数

        /// <summary>
        /// 离开限位 到 撞下一个限位 的 旋转架转动总角度 单位°
        /// </summary>
        public double RAngle { get; set; }


        /// <summary>
        /// 人字架 旋转1周 设置 时间。 
        /// 刚开机时,RenZiJiaPeriod = DefaultRPeriod;
        /// 异常时,RenZiJiaPeriod = DefaultRPeriod;
        /// </summary>
        public TimeSpan DefaultRPeriod { get; set; }


        /// <summary>
        /// 信号0 撞2次。 这个能自动设置。 两个撞同一信号 间隔30秒内,算一个换向信号。 
        /// 当为2次撞时:  StartTime = 第1次撞的StartTime, EndTime = 第2次撞的EndTime
        /// </summary>
        public bool IsSign0Double { get; set; }


        /// <summary>
        /// 信号1 撞2次。 这个能自动设置。 两个撞同一信号 间隔30秒内,算一个换向信号。 
        /// 当为2次撞时:  StartTime = 第1次撞的StartTime, EndTime = 第2次撞的EndTime
        /// </summary>
        public bool IsSign1Double { get; set; }

        /// <summary>
        /// 信号模式, 短信号模式(只撞一下),长信号模式(只要是转着,信号就长亮,直到换向)
        /// </summary>
        public BlowingSignType SignType { get; set; } = BlowingSignType.Short;




        /// <summary>
        /// 撞限位 到 离开限位 的时间, 需要初始值,以后测量出来的
        /// </summary>
        public TimeSpan LimitSignTime { get; set; }

        /// <summary>
        /// 人字架到测厚仪膜长 单位m
        /// </summary>
        public double FilmLength { get; set; }

        /// <summary>
        /// 辊周长,单位mm
        /// </summary>
        public double RollPerimeter { get; set; }

        /// <summary>
        /// 转向信号100ms 滤波
        /// </summary>
        public bool IsSignFilter { get; set; }


        /// <summary>
        /// 缺少信号模式
        /// 正反信号线接得不好,有时候会丢失了。 
        /// 当前的旋转时间是上一次的1.1倍时,在以前的位置增加信号!!!!
        /// 当有连续两次相同的信号,且信号差刚好是旋转时间的1.9~2.1倍时,证明真的是缺少信号
        /// </summary>
        public bool IsLackSignMode { get; set; }

        /// <summary>
        /// 旋转架为立式,测厚仪安装在二牵引前面,膜距离 与 旋转角度 一起变化, 旋转角度越大 膜距离越大
        /// </summary>
        public bool Is3D { get; set; }

        #endregion

        #region 状态

        /// <summary>
        /// 当前旋转架旋转角度
        /// </summary>
        public double Angle { get; set; }

        /// <summary>
        /// 当前方向
        /// </summary>
        public Misc.DIRECTION Direction { get; set; }

        /// <summary>
        /// 累计旋转次数
        /// </summary>
        public virtual int RotationCnt { get; protected set; }

        /// <summary>
        /// 最新的限位信号序号
        /// </summary>
        public int LastLimitNo { get; set; }

        /// <summary>
        /// 最后一个限位信号 到当前已经消耗的时间
        /// </summary>
        public TimeSpan PastTime { get; set; }

        /// <summary>
        /// 当前线速度,单位 m/min
        /// </summary>
        public double FilmVelocity { get; set; }

        /// <summary>
        /// 离开限位 到 撞下一个限位  的 旋转架转动时间, 需要初始值,以后测量出来的
        /// </summary>
        public TimeSpan RenZiJiaPeriod { get; set; }

        /// <summary>
        /// 缓冲区拥有数据的时间长度,1s更新一次
        /// </summary>
        public TimeSpan BufTotalTime { get; set; }
        #endregion

        #region 信号列表
        /// <summary>
        /// 信号列表第一个记录序号
        /// </summary>
        public int FirstSignBM { get; protected set; } = -1;

        /// <summary>
        /// 信号列表最后一个记录序号
        /// </summary>
        public int LastSignBM { get; protected set; } = -1;

        #endregion

        #region 成员变量

        /// <summary>
        /// 刚开机,信号列表一个信号都没有,创造一个时间点,作为信号点
        /// </summary>
        DateTime mDefaultTime;

        /// <summary>
        /// 辊信号列表
        /// </summary>
        RList<RollCell> mRollList;

        /// <summary>
        /// 转向信号列表
        /// </summary>
        public RList<LimitCell> mLimitList;

        /// <summary>
        /// 与mLimitList 一样!!
        /// 存在的目的用于看看信号是否正常
        /// </summary>
        RList<SignData> mSignList;

        /// <summary>
        /// 还没处理的信号0 列表
        /// </summary>
        List<SignData> mSign0ListNoJudge = new List<SignData>();

        /// <summary>
        /// 还没处理的信号1 列表
        /// </summary>
        List<SignData> mSign1ListNoJudge = new List<SignData>();


        /// <summary>
        /// 后触发,信号改变推送列表!!
        /// </summary>
        List<DateTime> mdtFilmInfoChangedEventList = new List<DateTime>();

        /// <summary>
        /// 当为 立式旋转架时,膜距离与旋转角度有关系
        /// 列表的长度为 200, 列表的头为信号0离开, 尾为信号1进入
        /// </summary>
        List<double> mFilmLength3D = new List<double>();
        #endregion

        /// <summary>
        /// 
        /// </summary>
        public BlowingDetect()
        {
            RAngle = 337;//°
            DefaultRPeriod = TimeSpan.FromMinutes(8);
            RenZiJiaPeriod = DefaultRPeriod;
            LimitSignTime = TimeSpan.FromSeconds(4);
            SignType = BlowingSignType.Short;
            IsSign0Double = false;
            IsSign1Double = false;
            IsLackSignMode = false;

            RollPerimeter = 314;//mm
            FilmLength = 25;//m


            Angle = 0;//°
            Direction = Misc.DIRECTION.FORWARD;
            FilmVelocity = 0;//m/min

            mRollList = new Misc.RList<RollCell>(4000);//以50m/min 线速度,储存大于 30min数据
            mLimitList = new Misc.RList<LimitCell>(20);//人字架转10次的信号
            mSignList = new RList<SignData>(30);//人字架正反转信号
            LastLimitNo = -1;
            RotationCnt = 0;


            Clear();

            PropertyChanged += new PropertyChangedEventHandler(RenZiJiaAndFilmPositionDetect_PropertyChanged);

        }


        int RotationCntToLimitIndex(int rotationcnt)
        {
            if (RotationCnt == 0)
                return -1;
            else
                return rotationcnt - 1 - (RotationCnt - mLimitList.Count);
        }
        /// <summary>
        /// 正反转信号列表中的序号,转旋转次数
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        int LimitIndexToRotationCnt(int index)
        {
            if (RotationCnt == 0)
                return 0;
            else
                return RotationCnt - mLimitList.Count + index + 1;
        }

        /// <summary>
        /// 使参数合法
        /// </summary>
        public void CheckParamErr()
        {
            if (RAngle < 100 || RAngle > 360)
            {
                RAngle = 350;
            }

            if ((DefaultRPeriod.TotalMinutes < 4) || (DefaultRPeriod.TotalMinutes > 25))
            {
                DefaultRPeriod = TimeSpan.FromMinutes(5.8);
            }

            if ((FilmLength < 0) || (FilmLength > 100))
            {
                FilmLength = 25;
            }

            if ((RollPerimeter < 100) || (RollPerimeter > 1000))
            {
                RollPerimeter = 314;
            }


            if (LimitSignTime.TotalSeconds > 30)
            {
                LimitSignTime = TimeSpan.FromSeconds(4);
            }

        }

        /// <summary>
        /// 触发膜信息修改事件
        /// </summary>
        /// <param name="dt">时间点</param>
        void TouchFilmInfoChanged(DateTime dt)
        {
            if (FilmInfoChangedEvent == null)
                return;

            mdtFilmInfoChangedEventList.Add(dt);

            TouchFilmInfoChanged();
        }
        /// <summary>
        /// 触发膜信息修改事件列表的全部事件
        /// </summary>
        void TouchFilmInfoChanged()
        {
            if (FilmInfoChangedEvent == null)
                return;

            while (mdtFilmInfoChangedEventList.Count > 0)
            {
                DateTime dt = mdtFilmInfoChangedEventList.First();
                DateTime dt_future;
                int ret = GetFuture(dt, out dt_future);
                if (ret == 1)//发生在信号列表的未来,留着,以后再触发!! 
                {
                    break;
                }
                else if (ret == 0)//发生在信号列表的中间
                {
                    //只需要触发最老的那个就够了!!!!!
                    mdtFilmInfoChangedEventList.Clear();
                    FilmInfoChangedEvent(this, new FilmInfoChangedEventArgs(dt_future));
                }
                else//发生在信号列表的以前
                {
                    //以前的事件不触发,除非就只有这么一个
                    mdtFilmInfoChangedEventList.RemoveAt(0);
                    if (mdtFilmInfoChangedEventList.Count == 0)
                    {
                        FilmInfoChangedEvent(this, new FilmInfoChangedEventArgs(dt_future));
                    }

                }
            }
        }
        //TODO
        void RenZiJiaAndFilmPositionDetect_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if ((e.PropertyName == nameof(FilmLength)) ||
                (e.PropertyName == nameof(RollPerimeter)) ||
                (e.PropertyName == nameof(RAngle))
            )
            {
                TouchFilmInfoChanged_lastLimit(4);
            }
            else if (e.PropertyName == nameof(RenZiJiaPeriod))
            {
                TouchFilmInfoChanged_lastLimit(1);
            }
            else if (e.PropertyName == nameof(DefaultRPeriod))
            {
                RenZiJiaPeriod = DefaultRPeriod;
            }
        }
        /// <summary>
        /// 触发膜信息修改事件
        /// </summary>
        /// <param name="last_cnt">最后旋转几次</param>
        void TouchFilmInfoChanged_lastLimit(int last_cnt)
        {
            if (mLimitList.Count < last_cnt)
            {
                last_cnt = mLimitList.Count;
            }

            int idx = mLimitList.Count - last_cnt - 1;

            if (idx <= 0)
            {
                TouchFilmInfoChanged(mDefaultTime);
            }
            else
            {
                TouchFilmInfoChanged(mLimitList[idx].dt_begin);
            }
        }

        /// <summary>
        /// 创建完对象后,必须赋值
        /// </summary>
        /// <param name="flyad"></param>
        public void Init(FlyADBase.IFlyAD flyad)
        {
            flyad.IStatusChangedEvent += new FlyADBase.IStatusChangedEventHandler(flyad_IStatusChangedEvent);
            FObjBase.PollModule.Current.Poll_Config(FObjBase.PollModule.POLL_CONFIG.ADD, new FObjBase.PollModule.PollHandler(OnPoll));
        }


        /// <summary>
        /// 正反信号滤波时间 150ms
        /// </summary>
        const int SIGN_DELAY_MS = 150;

        void update_limit(FlyADBase.IStatusChangedEventArgs e)
        {

            bool bchanged = false;//信号更新?
            DateTime dt = e.Time;//信号更新发生的时间
            int no = -1;//信号序号
            bool status = false;//信号的状态 true开始, false结束


            if (Misc.MyBase.CHECKBIT(e.IChanged, Limit0_bit - 1))//信号0 发生变化
            {
                bchanged = true;
                no = 0;
                if (!Misc.MyBase.CHECKBIT(e.IStatus, Limit0_bit - 1))
                {
                    status = true;
                }
            }
            else if (Misc.MyBase.CHECKBIT(e.IChanged, Limit1_bit - 1))//信号1 发生变化
            {
                bchanged = true;
                no = 1;
                if (!Misc.MyBase.CHECKBIT(e.IStatus, Limit1_bit - 1))
                {
                    status = true;
                }
            }

            if (!bchanged)//没有信号更新
                return;
            SignData sd = new SignData() { No = no, Time = dt, On = status };
            if (mSignList.Count() > 0)
            {
                SignData sd_last = mSignList.Last();
                sd.Interval = dt - sd_last.Time;
            }
            mSignList.RAdd(sd);

            FirstSignBM = mSignList.Index2No(0);
            LastSignBM = mSignList.GetLastNo();



            if (IsSignFilter)
            {
                //信号滤波
                if (no == 0)
                {
                    mSign0ListNoJudge.Add(mSignList.Last());
                    //等100ms再触发
                    FObjBase.PollModule.Current.Poll_Config(FObjBase.PollModule.POLL_CONFIG.ADD,
                        () =>
                        {
                            PopSign(0);
                        },
                        TimeSpan.FromMilliseconds(SIGN_DELAY_MS), true, false, this, MARKNO_DELAY0, true);
                }
                else
                {
                    mSign1ListNoJudge.Add(mSignList.Last());
                    //等100ms再触发
                    FObjBase.PollModule.Current.Poll_Config(FObjBase.PollModule.POLL_CONFIG.ADD,
                        () =>
                        {

                            PopSign(1);
                        },
                        TimeSpan.FromMilliseconds(SIGN_DELAY_MS), true, false, this, MARKNO_DELAY1, true);
                }
            }
            else
            {
                update_limit(dt, no, status);
            }
        }
        /// <summary>
        /// 判断信号是否毛刺,最后会调用update_limit
        /// </summary>
        /// <param name="no"></param>
        void PopSign(int no)
        {
            List<SignData> noJudgeList;
            if (no == 0)
                noJudgeList = mSign0ListNoJudge;
            else
                noJudgeList = mSign1ListNoJudge;

            if (noJudgeList.Count() == 0)
            {
                //没有信号,异常
                noJudgeList.Clear();
                return;
            }

            if (noJudgeList.Count() == 1)
            {
                //只有一个信号,它持续了100ms,是信号不是毛刺
                update_limit(noJudgeList.First());
            }
            else
            {
                SignData sd0 = noJudgeList.First();
                SignData sd1 = noJudgeList.Last();
                if (sd0.On == sd1.On)
                {
                    //状态一样, 不应该会发生的情况,AD卡坏了?
                    //异常处理:两次信号间的都只是毛刺,触发第1个信号
                    update_limit(sd0);
                }
                else
                {
                    if (sd1.Time - sd0.Time >= TimeSpan.FromMilliseconds(SIGN_DELAY_MS))
                    {
                        //两个信号时间差大于 100ms 
                        //触发它们
                        update_limit(sd0);
                        update_limit(sd1);
                    }
                    else
                    {
                        //时间差在100ms内,只是毛刺而已
                    }
                }
            }
            //判断完,清空列表!!!
            noJudgeList.Clear();
        }


        /// <summary>
        /// 添加正反转信号到 列表
        /// </summary>
        /// <param name="s"></param>
        void update_limit(SignData s)
        {
            update_limit(s.Time, s.No, s.On);
        }


        /// <summary>
        /// 添加正反转信号到 列表
        /// </summary>
        /// <param name="dt">信号更新发生的时间</param>
        /// <param name="no">信号序号</param>
        /// <param name="status">信号的状态 true开始, false结束</param>
        void update_limit(DateTime dt, int no, bool status)
        {
            //上一个是虚拟信号
            //1.当接收的信号与虚拟信号相同时,把虚拟信号删除!!!!!
            //2.当接收的信号与虚拟信号不同时,更新旋转时间!!!!!
            if (!update_limit_virtual(dt, no, status))
            {
                LastLimitNo = no;

                switch (SignType)
                {
                    case BlowingSignType.Short:
                        update_limit_short(dt, no, status);
                        break;
                    default:
                        update_limit_long(dt, no, status);
                        break;
                }
            }


            //更新其它参数
            update_param_for_limit(dt);
        }

        /// <summary>
        /// 更新因为旋转信号的添加导致的变化
        /// </summary>
        /// <param name="dt">信号变化的时间</param>
        void update_param_for_limit(DateTime dt)
        {
            if (mLimitList.Count() > 0)
            {
                LimitCell lc = mLimitList.Last();

                if (dt == lc.dt_begin)//刚撞转向
                {
                    //RotationCnt++;
                    //更新人字架旋转周期
                    if (mLimitList.Count > 1)
                    {
                        DateTime dt1 = mLimitList[mLimitList.Count - 1].dt_begin;
                        DateTime dt2 = mLimitList[mLimitList.Count - 2].dt_end;
                        RenZiJiaPeriod = dt1 - dt2;//只有这个是正确的!!!
                                                   //触发修正事件!!!!!
                        TouchFilmInfoChanged(dt2);
                    }
                    else if (mLimitList.Count == 1)
                    {
                        DateTime dt1 = mLimitList[mLimitList.Count - 1].dt_begin;
                        mDefaultTime = dt1 - RenZiJiaPeriod;
                        //触发修正事件!!!!!
                        TouchFilmInfoChanged(mDefaultTime);
                    }
                }
                else if (lc.dt_end != DateTime.MinValue)
                    LimitSignTime = lc.dt_end - lc.dt_begin;//更新撞信号的耗时
            }
        }

        /// <summary>
        /// 处理上一个是虚拟信号的正反转信号,它可能是多余的,返回true,新来的真实信号已经被处理
        /// </summary>
        /// <param name="dt"></param>
        /// <param name="no"></param>
        /// <param name="status"></param>
        /// <returns></returns>
        bool update_limit_virtual(DateTime dt, int no, bool status)
        {
            if (mLimitList.Count() == 0)
                return false;

            LimitCell lc_v = mLimitList.Last();
            if (lc_v.isVirtual == false)//不是虚拟信号,不用再判断了
                return false;

            if (lc_v.no == no)//有真实的信号,与虚拟信号一样
            {
                if (status != true)
                    return false;//异常, 不可能在 真实的信号中间插了个虚拟信号

                //上一个信号是多余的
                lc_v.isVirtual = false;
                lc_v.dt_begin = dt;
                lc_v.dt_end = DateTime.MinValue;
                return true;
            }
            else
            {
                //更新旋转时间!!!!!!
                if (mLimitList.Count() > 1)
                {
                    DateTime dt2 = mLimitList[mLimitList.Count - 2].dt_end;

                    RenZiJiaPeriod = TimeSpan.FromTicks((dt - dt2 - LimitSignTime).Ticks / 2);//只有这个是正确的!!!

                    lc_v.dt_begin = dt2 + RenZiJiaPeriod;
                    lc_v.dt_end = lc_v.dt_begin + LimitSignTime;
                }
            }
            return false;
        }
        /// <summary>
        /// 处理短信号
        /// </summary>
        /// <param name="dt">信号发生的时间</param>
        /// <param name="no">信号序号</param>
        /// <param name="status">信号的状态 true开始, false结束)</param>
        void update_limit_short(DateTime dt, int no, bool status)
        {
            //把信号加入到列表

            if (status == true)//添加,写入开始时间
            {
                if (mLimitList.Count > 0)
                {
                    LimitCell lc = mLimitList.Last();
                    if (lc.no == no)//连续撞了两次相同限位
                    {
                        if (lc.isDouble)
                        {
                            lc.dt_end = dt;//信号结束了
                            LimitSignTime = lc.dt_end - lc.dt_begin;//更新撞限位的耗时
                        }
                        else
                        {
                            if (lc.dt_end == DateTime.MinValue)//上一个限位还没离开
                            {
                                //异常,AD卡出错了,或者断开连接了,清空!!!
                                mLimitList.Clear();

                                mLimitList.RAdd(
                                    new LimitCell()
                                    {
                                        dt_begin = dt,
                                        no = no,
                                        isDouble = (no == 0) ? IsSign0Double : IsSign1Double
                                    });
                                RotationCnt++;
                            }
                            else
                            {
                                //判断两次的时间差
                                if ((dt - lc.dt_end) < TimeSpan.FromSeconds(30))
                                {
                                    //这是同一个转向信号
                                    lc.isDouble = true;
                                    if (lc.no == 0)
                                        IsSign0Double = lc.isDouble;
                                    else
                                        IsSign1Double = lc.isDouble;

                                    lc.dt_mid = lc.dt_end;
                                    lc.dt_end = dt;
                                }
                                else
                                {
                                    //时间差很大, 异常,旋转架重新转启
                                    //异常,AD卡出错了,或者断开连接了,清空!!!
                                    mLimitList.Clear();

                                    mLimitList.RAdd(
                                        new LimitCell()
                                        {
                                            dt_begin = dt,
                                            no = no,
                                            isDouble = (no == 0) ? IsSign0Double : IsSign1Double
                                        });
                                    RotationCnt++;
                                }
                            }
                        }
                    }
                    else
                    {
                        //一个新的 信号

                        if (lc.dt_end == DateTime.MinValue)//上一个转向还没离开
                        {
                            if (lc.isDouble)
                            {
                                if (lc.dt_mid != DateTime.MinValue)
                                {
                                    //没事, 只是从需要撞两次,变成只撞一次就可以了

                                    lc.isDouble = false;
                                    if (lc.no == 0)
                                        IsSign0Double = lc.isDouble;
                                    else
                                        IsSign1Double = lc.isDouble;

                                    lc.dt_end = lc.dt_mid;
                                    lc.dt_mid = DateTime.MinValue;
                                }
                                else
                                {
                                    //异常 输入口没有结束
                                    //AD卡出错了,或者断开连接了,清空!!!
                                    mLimitList.Clear();

                                    mLimitList.RAdd(
                                        new LimitCell()
                                        {
                                            dt_begin = dt,
                                            no = no,
                                            isDouble = (no == 0) ? IsSign0Double : IsSign1Double
                                        });
                                    RotationCnt++;
                                }
                            }
                            else
                            {
                                //异常 输入口没有结束
                                //AD卡出错了,或者断开连接了,清空!!!
                                mLimitList.Clear();

                                mLimitList.RAdd(
                                    new LimitCell()
                                    {
                                        dt_begin = dt,
                                        no = no,
                                        isDouble = (no == 0) ? IsSign0Double : IsSign1Double
                                    });
                                RotationCnt++;
                            }
                        }
                        else
                        {
                            //一个新的信号
                            mLimitList.RAdd(
                            new LimitCell()
                            {
                                dt_begin = dt,
                                no = no,
                                isDouble = (no == 0) ? IsSign0Double : IsSign1Double
                            });
                            RotationCnt++;
                        }
                    }
                }
                else
                {
                    //一个新的信号
                    mLimitList.RAdd(
                    new LimitCell()
                    {
                        dt_begin = dt,
                        no = no,
                        isDouble = (no == 0) ? IsSign0Double : IsSign1Double
                    });
                    RotationCnt++;
                }
            }
            else //写入 结束时间
            {
                if (mLimitList.Count > 0)
                {
                    LimitCell lc = mLimitList.Last();
                    if (lc.no == no)
                    {
                        if (lc.isDouble)
                        {
                            //需要撞2次
                            if (lc.dt_mid == DateTime.MinValue)
                            {
                                lc.dt_mid = dt;//完成撞第1次
                            }
                            else if (lc.dt_end != DateTime.MinValue)
                            {
                                //完成撞第2次
                            }
                            else
                            {
                                //异常, 撞第2次 还没开始,就结束了
                                //AD卡出错了,或者断开连接了,清空!!!
                                mLimitList.Clear();
                            }
                        }
                        else
                        {
                            if (lc.dt_end == DateTime.MinValue)
                            {
                                lc.dt_end = dt;//完成一次信号
                            }
                            else
                            {
                                //异常,连续2次结束
                                mLimitList.Clear();
                            }
                        }
                    }
                    else
                    {
                        //异常,还没开始,就结束了
                        mLimitList.Clear();
                    }
                }
                else
                {
                    //异常,还没开始,就结束了
                }
            }
        }

        /// <summary>
        /// 处理长信号, 只关心 status=true
        /// </summary>
        /// <param name="dt">信号发生的时间</param>
        /// <param name="no">信号序号</param>
        /// <param name="status">信号的状态 true开始, false结束)</param>
        void update_limit_long(DateTime dt, int no, bool status)
        {
            //把信号加入到列表

            if (status == true)//添加,写入开始时间
            {
                if (mLimitList.Count > 0)
                {
                    LimitCell lc = mLimitList.Last();
                    if (lc.no == no)//连续撞了两次相同限位, 可能重新在转
                    {
                        mLimitList.Clear();
                    }
                }
                mLimitList.RAdd(
                    new LimitCell()
                    {
                        no = no,
                        dt_begin = dt,
                        dt_end = dt + LimitSignTime
                    });
                RotationCnt++;
            }
        }

        void update_roll(FlyADBase.IStatusChangedEventArgs e)
        {
            //----------------------------------------------------------------------
            //更新辊信号
            if (!Misc.MyBase.CHECKBIT(e.IChanged, Roll_bit - 1))
                return;//没有信号更新

            //信号的状态
            bool status = !Misc.MyBase.CHECKBIT(e.IStatus, Roll_bit - 1);

            DateTime dt = e.Time;//信号更新发生的时间


            if (status)//1->0 触发!!!
            {
                mRollList.RAdd(
                    new RollCell()
                    {
                        dt = dt
                    });


                //触发修正事件!!!!!
                TouchFilmInfoChanged();
            }
        }
        void flyad_IStatusChangedEvent(object sender, FlyADBase.IStatusChangedEventArgs e)
        {
            update_limit(e);
            update_roll(e);

        }

        /// <summary>
        /// 1秒触发一次
        /// </summary>
        void OnPoll()
        {
            //--------------------------------------------------------------------
            //添加虚拟信号
            if (IsLackSignMode)
                OnPoll_Virtual();

            //--------------------------------------------------------------------
            //正反向信号撞两次的超时判断
            OnPoll_checkDouble();

            //--------------------------------------------------------------------
            //旋转架速度安全检测
            OnPoll_checkRVelocity();

            //--------------------------------------------------------------------
            //当前旋转架角度 
            OnPoll_updateRV();

            //--------------------------------------------------------------------
            //线速度安全检测
            OnPoll_checkFilmV();

            //--------------------------------------------------------------------
            //更新当前线速度
            OnPoll_updateFilmV();
        }
        /// <summary>
        /// 虚拟信号添加
        /// </summary>
        void OnPoll_Virtual()
        {
            DateTime now = DateTime.Now;

            //--------------------------------------------------------------------
            //正反向信号撞两次的超时判断
            if (mLimitList.Count > 0)
            {
                LimitCell lc = mLimitList.Last();
                if (!lc.isVirtual)//上一个不能是虚拟信号
                {
                    if (lc.dt_end != DateTime.MinValue)//上一个信号完成了
                    {
                        if (now - lc.dt_end > TimeSpan.FromSeconds(RenZiJiaPeriod.TotalSeconds + 20))
                        {
                            //已经大于上一次旋转信号+20s,都没有信号了
                            //添加虚拟信号
                            int no = (lc.no == 0) ? 1 : 0;
                            DateTime dt = lc.dt_end + RenZiJiaPeriod;
                            mLimitList.RAdd(new LimitCell()
                            {
                                no = no,
                                dt_begin = dt,
                                dt_end = dt + LimitSignTime,
                                isVirtual = true
                            });
                            RotationCnt++;
                            LastLimitNo = no;
                            update_param_for_limit(dt);
                        }
                    }
                }
            }
        }
        /// <summary>
        /// 正反向信号撞两次的超时判断
        /// </summary>
        void OnPoll_checkDouble()
        {
            if (mLimitList.Count > 0)
            {
                LimitCell lc = mLimitList.Last();
                //需要撞两次的超时判断!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
                if (lc.isDouble)
                {
                    if (lc.dt_end == DateTime.MinValue)
                    {
                        //第2次还没撞
                        if (DateTime.Now - lc.dt_begin > TimeSpan.FromSeconds(30))
                        {
                            //长时间第2次都没撞, 看来只需要撞一次而已
                            lc.isDouble = false;
                            if (lc.no == 0)
                                IsSign0Double = lc.isDouble;
                            else
                                IsSign1Double = lc.isDouble;
                            lc.dt_end = lc.dt_mid;
                            lc.dt_mid = DateTime.MinValue;
                        }
                    }
                }
            }
        }
        /// <summary>
        /// 旋转架速度安全检测
        /// </summary>
        void OnPoll_checkRVelocity()
        {
            DateTime dt = mDefaultTime;
            if (mLimitList.Count > 0)
            {
                LimitCell lc = mLimitList.Last();
                if (lc.dt_end != DateTime.MinValue)
                    dt = lc.dt_end;
                else
                    dt = lc.dt_begin;
            }

            PastTime = DateTime.Now - dt;
            if (PastTime > TimeSpan.FromMinutes(25))//25min 
            {
                //大于25分钟,也没有一个信号,复位
                Clear();
            }
        }
        /// <summary>
        /// 更新旋转架速度
        /// </summary>
        void OnPoll_updateRV()
        {
            double angle;
            DIRECTION direction;
            LimitListSearch(DateTime.Now, false, out angle, out direction, out int rotationCnt, out bool inCV);
            Angle = angle;
            Direction = direction;
        }
        /// <summary>
        /// 更新当前线速度
        /// </summary>
        void OnPoll_updateFilmV()
        {
            if (mRollList.Count > 1)
            {
                DateTime dt1 = mRollList[mRollList.Count - 1].dt;
                int cnt = 5;
                if (cnt > mRollList.Count)
                    cnt = mRollList.Count;


                DateTime dt2 = mRollList[mRollList.Count - cnt].dt;

                DateTime dt11 = mRollList[mRollList.Count - (cnt - 1)].dt;

                TimeSpan ts_last = TimeSpan.FromTicks((dt1 - dt2).Ticks / (cnt - 1));//5次的平均滤波
                TimeSpan ts_now = TimeSpan.FromTicks((DateTime.Now - dt11).Ticks / (cnt - 1));
                TimeSpan ts_max;
                if (ts_now > (ts_last + ts_last))
                {
                    ts_max = ts_now;
                }
                else
                {
                    ts_max = ts_last;
                }

                FilmVelocity = RollPerimeter / 1000.0 / ts_max.TotalMinutes;

                BufTotalTime = DateTime.Now - mRollList[0].dt;
            }
        }
        /// <summary>
        /// 线速度安全检测
        /// </summary>
        void OnPoll_checkFilmV()
        {
            if (mRollList.Count > 0)
            {
                DateTime dt = mRollList[mRollList.Count - 1].dt;
                TimeSpan ts = DateTime.Now - dt;
                if (ts.TotalMinutes > 1)//1min 
                {
                    //大于1分钟,也没有一个信号,复位
                    Clear();
                }
            }
        }

        public void Clear()
        {


            mLimitList.Clear();
            mRollList.Clear();

            mDefaultTime = DateTime.Now;
            FilmVelocity = 0;
            RenZiJiaPeriod = DefaultRPeriod;
            RotationCnt = 0;//复位次数!!!
            LimitSignTime = TimeSpan.FromSeconds(4);

            BufTotalTime = TimeSpan.Zero;
            ClearEvent?.Invoke(this);
            
        }

        public class FilmInfo
        {
            /// <summary>
            /// 上层 位于膜泡的角度 0~360°
            /// </summary>
            public double angle1;
            /// <summary>
            /// 下层 位于膜泡的角度 0~360°
            /// </summary>
            public double angle2;
            /// <summary>
            /// 膜泡旋转方向
            /// </summary>
            public Misc.DIRECTION direction;
            /// <summary>
            /// 线速度
            /// </summary>
            public double filmVelocity;
            /// <summary>
            /// 累计旋转次数
            /// </summary>
            public int rotationCnt;
            /// <summary>
            /// 在匀速阶段; 在限位信号 开始~结束 期间 inCV = false
            /// </summary>
            public bool inCV;
        }




        public enum GetLimitTimeResult
        {
            /// <summary>
            /// 获取成功
            /// </summary>
            OK,
            /// <summary>
            /// 不存在该点
            /// </summary>
            NOEXIST,
            /// <summary>
            /// 限位已经撞了,但膜还没走完,等吧
            /// </summary>
            ISFURTURE
        }

        /// <summary>
        /// 通过角度 获取 膜距离
        /// </summary>
        /// <param name="angle">-180~180</param>
        /// <returns></returns>
        double GetFilmLength3D(double angle)
        {
            if (mFilmLength3D == null || mFilmLength3D.Count() == 0)
                return FilmLength;


            angle += RAngle / 2;
            int idx = (int)(mFilmLength3D.Count() * angle / RAngle);
            if (idx < 0) idx = 0;
            else if (idx >= mFilmLength3D.Count()) idx = mFilmLength3D.Count() - 1;
            return FilmLength + mFilmLength3D[idx];
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="dt"></param>
        /// <returns></returns>
        double GetFilmLength3D(DateTime dt)
        {
            GetAngle(0, dt, out double angle0, out double angle1, out DIRECTION direction, out int rotationcnt, out bool inCV);

            return GetFilmLength3D(angle0);
        }

        /// <summary>
        /// 计算膜长用;
        /// 输入膜长,获取对应的 限位点时间
        /// </summary>
        /// <param name="rotationcnt">旋转次数</param>
        /// <param name="filmlength">膜长</param>
        /// <param name="timeOfFilmLength">膜长对应限位时间</param>
        /// <returns></returns>
        public GetLimitTimeResult GetLimitTime(int rotationcnt, double filmlength, out DateTime timeOfFilmLength)
        {
            timeOfFilmLength = DateTime.MinValue;
            int index = RotationCntToLimitIndex(rotationcnt);
            if (index == -1)
                return GetLimitTimeResult.NOEXIST;
            if (index >= mLimitList.Count)
                return GetLimitTimeResult.ISFURTURE;

            DateTime dt = mLimitList[index].dt_begin;
            int ret = GetFuture(dt, filmlength, out timeOfFilmLength);
            if (ret == 1)
                return GetLimitTimeResult.ISFURTURE;

            return GetLimitTimeResult.OK;
        }


        /// <summary>
        /// 获取当前在膜上的测量点,对应于膜泡的角度信息, 当时间点,比LimitList.Last.dt+RenZiJiaPeriod还要后,更新RenZiJiaPeriod
        /// 返回 -1, 当前的时间点在列表以前;
        /// 返回 0, 当前的时间点在列表中;
        /// 返回 1, 当前的时间点在列表的未来;
        /// </summary>
        /// <param name="filmWidth">被压扁后的膜宽度</param>
        /// <param name="filmPosH">探头所在膜的横向位置</param>
        /// <param name="dt">测量的时间点</param>
        /// <returns></returns>
        public int GetFilmInfo(out FilmInfo filminfo, DateTime dt, double filmWidth, double filmPosH)
        {
            double filmlen = FilmLength;
            if (Is3D)
                filmlen = GetFilmLength3D(dt);

            return GetFilmInfo(out filminfo, dt, filmWidth, filmPosH, filmlen);
        }
        int GetFilmInfo(out FilmInfo filminfo, DateTime dt, double filmWidth, double filmPosH, double filmLen)
        {
            filminfo = null;

            //与0°的偏移
            double a = filmPosH / filmWidth * 180;

            if ((a < 0) || (a > 180))
            {
                return -1;//不在膜的范围内
            }

            DateTime ago_dt;
            double velocity;
            double angle0;
            double angle1;
            Misc.DIRECTION direction;
            int rotationcnt;
            bool inCV;
            if (dt == DateTime.MinValue)
            {
                //TODO, BUG
            }
            int ret = GetAgo(dt, filmLen, out ago_dt, out velocity);

            if (ret == 1)
                return ret;//发生在未来不处理!!!
            GetAngle(a, ago_dt, out angle0, out angle1, out direction, out rotationcnt, out inCV);

            filminfo = new FilmInfo()
            {
                angle1 = angle0,
                angle2 = angle1,
                filmVelocity = velocity,
                direction = direction,
                rotationCnt = rotationcnt,
                inCV = inCV
            };
            return ret;
        }

        #region 人字架时间点<-->24m后膜时间点
        /// <summary>
        /// 输入当前时间点,找到 刚出人字架时的时间点, 及那时候的线速度;
        /// 返回 -1, 当前的时间点在列表以前;
        /// 返回 0, 当前的时间点在列表中;
        /// 返回 1, 当前的时间点在列表的未来;
        /// </summary>
        /// <param name="dt">当前时间</param>
        /// <param name="ago_dt">24米以前的时间</param>
        /// <returns></returns>
        int GetAgo(DateTime dt, out DateTime ago_dt, out double ago_velocity)
        {
            return GetAgo(dt, FilmLength, out ago_dt, out ago_velocity);
        }
        /// <summary>
        /// 输入当前时间点,找到 刚出人字架时的时间点, 及那时候的线速度;
        /// 返回 -1, 当前的时间点在列表以前;
        /// 返回 0, 当前的时间点在列表中;
        /// 返回 1, 当前的时间点在列表的未来;
        /// </summary>
        /// <param name="dt">当前时间</param>
        /// <param name="ago_dt">24米真实长度</param>
        /// <returns></returns>
        int GetAgo(DateTime dt, double filmlength, out DateTime ago_dt, out double ago_velocity)
        {

            ago_dt = DateTime.MinValue;
            ago_velocity = 0;
            int i;
            int ret = RollList_FindIndex(dt, out i);
            if (ret == 1)
            {
                return ret;
            }

            double position = RollList_GetPosition(i, dt);
            position -= filmlength * 1000;
            ret = RollList_GetDateTime(out i, position, out ago_dt);
            ago_velocity = RollList_GetVelocity(i);
            return ret;
        }
        /// <summary>
        /// 输入过去时间,让它再加上膜长时间,得到未来时间;
        /// 返回 -1, 未来的时间在列表以前;
        /// 返回 0, 未来的时间在列表中;
        /// 返回 1, 未来的时间在列表的未来;
        /// </summary>
        /// <param name="dt">过去时间</param>
        /// <param name="dt_future">未来时间</param>
        /// <returns></returns>
        int GetFuture(DateTime dt, out DateTime dt_future)
        {
            return GetFuture(dt, FilmLength, out dt_future);
        }
        /// <summary>
        /// 输入过去时间,让它再加上膜长时间,得到未来时间;
        /// 返回 -1, 未来的时间在列表以前;
        /// 返回 0, 未来的时间在列表中;
        /// 返回 1, 未来的时间在列表的未来;
        /// </summary>
        /// <param name="dt">过去时间</param>
        /// <param name="filmlength">膜长</param>
        /// <param name="dt_future">未来时间</param>
        /// <returns></returns>
        int GetFuture(DateTime dt, double filmlength, out DateTime dt_future)
        {
            dt_future = DateTime.MinValue;
            double position;
            int i;
            int ret = RollList_FindIndex(dt, out i);
            if (ret == -1)
                position = RollList_GetPosition(0, dt);
            else if (ret == 0)
                position = RollList_GetPosition(i, dt);
            else
                return ret;

            position += filmlength * 1000;

            return RollList_GetDateTime(out i, position, out dt_future);

        }

        #endregion

        /// <summary>
        /// 计算出角度 -180~180 恒速阶段
        /// </summary>
        /// <param name="dtEnd">旋转结束时间点</param>
        /// <param name="dtStart">旋转开始时间点</param>
        /// <param name="noStart">开始时间点对应的限位 0 or 1</param>
        /// <param name="dt">用于转换为角度的时间</param>
        /// <param name="angle">输出: 角度</param>
        /// <param name="direction">输出: 方向</param>
        void LimitListCal_ConstantVelocity(DateTime dtEnd, DateTime dtStart, int noStart, DateTime dt, out double angle, out Misc.DIRECTION direction)
        {
            TimeSpan ts1 = dt - dtStart;
            TimeSpan ts2 = dtEnd - dtStart;

            angle = RAngle * ts1.Ticks / ts2.Ticks;

            if (noStart == 0)
            {
                angle = -RAngle / 2 + angle;
                direction = Misc.DIRECTION.FORWARD;
            }
            else
            {
                angle = RAngle / 2 - angle;
                direction = Misc.DIRECTION.BACKWARD;
            }
        }


        /// <summary>
        /// 时间点查找旋转角度 -180~180
        /// </summary>
        /// <param name="dt">时间</param>
        /// <param name="modifyparam">当dt 发生在限位列表的未来,是否修改参数,如RenZiJiaPeriod</param>
        /// <param name="angle">角度</param>
        /// <param name="direction">旋转的方向</param>
        /// <param name="rotationCnt">旋转的次数</param>
        /// <param name="inCV">在恒速区</param>
        void LimitListSearch(DateTime dt, bool modifyparam, out double angle, out Misc.DIRECTION direction, out int rotationCnt, out bool inCV)
        {
            int index;
            ListFindIndexResult ret = LimitList_FindIndex(dt, out index);

            DateTime dt_start;
            DateTime dt_end;
            int no;

            switch (ret)
            {
                case ListFindIndexResult.Future://发生在未来 
                    {
                        if (dt < mDefaultTime)
                            mDefaultTime = dt - TimeSpan.FromSeconds(10);

                        //检测是否需要修正 RenZiJiaPeriod
                        if (mLimitList.Count == 0)
                        {
                            dt_start = mDefaultTime;
                            no = 0;
                        }
                        else
                        {
                            dt_start = mLimitList[index].dt_end;
                            no = mLimitList[index].no;
                        }
                        rotationCnt = LimitIndexToRotationCnt(index);

                        if (dt > (dt_start + RenZiJiaPeriod))
                        {
                            if (modifyparam)
                            {
                                //修正!!!!
                                RenZiJiaPeriod = dt - dt_start + TimeSpan.FromSeconds(10);
                            }
                            else
                            {
                                dt = dt_start + RenZiJiaPeriod;//只是为了看,随便了。 时间停止下来
                            }
                        }

                        dt_end = dt_start + RenZiJiaPeriod;
                        inCV = true;
                        LimitListCal_ConstantVelocity(dt_end, dt_start, no, dt, out angle, out direction);
                    }
                    break;
                case ListFindIndexResult.BetweenCells:
                    {
                        dt_start = mLimitList[index].dt_end;
                        dt_end = mLimitList[index + 1].dt_begin;
                        no = mLimitList[index].no;
                        rotationCnt = LimitIndexToRotationCnt(index);
                        inCV = true;
                        LimitListCal_ConstantVelocity(dt_end, dt_start, no, dt, out angle, out direction);
                    }
                    break;
                case ListFindIndexResult.Past://过去 
                    {
                        
                        dt_start = mDefaultTime;
                        dt_end = mLimitList[0].dt_begin;
                        no = (mLimitList[0].no == 0) ? 1 : 0;
                        rotationCnt = LimitIndexToRotationCnt(-1);
                        inCV = true;
                        LimitListCal_ConstantVelocity(dt_end, dt_start, no, dt, out angle, out direction);
                    }
                    break;
                //case ListFindIndexResult.InCell:
                //case ListFindIndexResult.MaybeInCell:
                default:
                    {

                        //发生在 撞换向信号中间, 简单当它没有动就算了
                        inCV = false;
                        no = mLimitList[index].no;
                        rotationCnt = LimitIndexToRotationCnt(index);
                        if (no == 0)
                        {
                            angle = -RAngle / 2;
                            direction = DIRECTION.FORWARD;
                        }
                        else { 
                            angle = RAngle / 2;
                            direction = DIRECTION.BACKWARD;
                        }
                    }
                    break;
            }
        }

        #region RollList 查找
        /// <summary>
        /// 获取 [idx,idx+1] 之间 的 位置
        /// </summary>
        /// <param name="idx"></param>
        /// <param name="dt"></param>
        /// <returns></returns>
        double RollList_GetPosition(int idx, DateTime dt)
        {
            DateTime dt1 = mRollList[idx + 1].dt;
            double position1 = (idx + 1) * RollPerimeter;
            DateTime dt2 = mRollList[idx].dt;
            double position2 = (idx) * RollPerimeter;

            return (position1 - position2) * (dt - dt2).Ticks / (dt1 - dt2).Ticks + position2;
        }
        /// <summary>
        /// 获取 [idx,idx+1] 之间 的 速度
        /// </summary>
        /// <param name="idx"></param>
        /// <returns></returns>
        double RollList_GetVelocity(int idx)
        {
            DateTime dt1 = mRollList[idx + 1].dt;
            DateTime dt2 = mRollList[idx].dt;

            return RollPerimeter / 1000 / dt1.Subtract(dt2).TotalMinutes;
        }
        /// <summary>
        /// 获取 [idx,idx+1] 之间 的 时间
        /// </summary>
        /// <param name="idx"></param>
        /// <param name="position"></param>
        /// <returns></returns>
        DateTime RollList_GetDateTime(int idx, double position)
        {
            //TODO 经常出错,position 无限小,idx=0
            DateTime dt1 = mRollList[idx + 1].dt;
            double position1 = (idx + 1) * RollPerimeter;
            DateTime dt2 = mRollList[idx].dt;
            double position2 = (idx) * RollPerimeter;

            return dt2.Add(new TimeSpan((long)((dt1 - dt2).Ticks * (position - position2) / (position1 - position2))));
        }

        /// <summary>
        /// 通过 position 查找时间
        /// 返回 -1,这个过去的时间;
        /// 返回 0,在列表中;
        /// 返回 1,发生在未来的时间;
        /// </summary>
        /// <param name="position"></param>
        /// <returns></returns>
        int RollList_GetDateTime(out int idx, double position, out DateTime dt)
        {
            idx = 0;
            dt = DateTime.MinValue;

            if (mRollList.Count == 0)
                return 1;

            for (int i = mRollList.Count - 1; i > 0; i--)
            {
                if (position >= ((i) * RollPerimeter))
                {
                    //找到了
                    idx = i;
                    if (i == (mRollList.Count - 1))
                        return 1;
                    else
                    {
                        dt = RollList_GetDateTime(i, position);
                        return 0;
                    }

                }
            }
            //TODO, 这里会出错,position无限小
            idx = 0;
            dt = RollList_GetDateTime(0, position);
            //发生在过去
            return -1;
        }

        /// <summary>
        /// dt 这个时间,在是 index 与 index+1 中间发生的
        /// 返回 -1,这个过去的时间;
        /// 返回 0,在列表中;
        /// 返回 1,发生在未来的时间;
        /// </summary>
        /// <param name="dt"></param>
        /// <returns></returns>
        int RollList_FindIndex(DateTime dt, out int index)
        {
            index = 0;
            if (mRollList.Count <= 1)//没数据,所以是发生在未来的
                return 1;

            for (int i = mRollList.Count - 1; i >= 0; i--)
            {
                if (dt >= mRollList[i].dt)
                {
                    //找到了
                    if (i == (mRollList.Count - 1))//这个是未来的时间点,还没发生!!! 
                    {
                        index = i;
                        return 1;
                    }
                    else
                    {
                        index = i;
                        return 0;
                    }
                }
            }

            //这个在过去的时间
            return -1;
        }

        #endregion

        /// <summary>
        /// 时间对应旋转状态
        /// </summary>
        enum DTSTATUS
        {
            /// <summary>
            /// 发生在未来的均速阶段
            /// </summary>
            Future,
            /// <summary>
            /// 发生在过去的均速阶段
            /// </summary>
            Past,
            /// <summary>
            /// 发生在均速阶段
            /// </summary>
            Normal,
            /// <summary>
            /// 发生在方向切换阶段
            /// </summary>
            Swap,

        }
        enum ListFindIndexResult
        {
            /// <summary>
            /// 发生在未来, index.dt_end ~ 未来, 可能 index都还没有发生
            /// </summary>
            Future,
            /// <summary>
            /// 发生在index.dt_begin ~ index.dt_end
            /// </summary>
            InCell,
            /// <summary>
            /// 应该发生在index.dt_begin ~ index.dt_end, 但 index.dt_end还没有数据
            /// </summary>
            MaybeInCell,
            /// <summary>
            /// 发生在 index.dt_end ~ (index+1).dt_begin
            /// </summary>
            BetweenCells,
            /// <summary>
            /// 发生在 过去
            /// </summary>
            Past
        }
        /// <summary>
        /// 找到 列表中 (开始时间,结束时间) 小于 dt 的信号点
        /// </summary>
        /// <param name="dt"></param>
        /// <param name="index"></param>
        /// <returns></returns>
        ListFindIndexResult LimitList_FindIndex(DateTime dt, out int index)
        {
            index = 0;
            if (mLimitList.Count <= 0)//没数据,所以是发生在未来的
                return ListFindIndexResult.Future;

            for (int i = mLimitList.Count - 1; i >= 0; i--)
            {
                LimitCell lc = mLimitList[i];
                if (dt >= lc.dt_begin)
                {
                    //找到了

                    index = i;
                    if (lc.dt_end == DateTime.MinValue)
                    {
                        return ListFindIndexResult.MaybeInCell;
                    }
                    else if (dt <= mLimitList[i].dt_end)
                    {
                        return ListFindIndexResult.InCell;
                    }
                    else if (i == (mLimitList.Count - 1))//这个是未来的时间点,还没发生!!! 
                    {
                        return ListFindIndexResult.Future;
                    }
                    else
                    {
                        return ListFindIndexResult.BetweenCells;
                    }
                }
            }

            //这个在过去的时间
            return ListFindIndexResult.Past;
        }



        /// <summary>
        /// 获取膜角度, TODO
        /// </summary>
        /// <param name="a">与0°的偏移, 0~180</param>
        /// <param name="dt">时间点</param>
        /// <param name="angle0">上面的膜对应的膜角度</param>
        /// <param name="angle1">下面的膜对应的膜角度</param>
        /// <param name="direction">人字架正转?反转? FIX, 停止!</param>
        /// <param name="rotationCnt">扫描次数</param>
        /// <param name="inCV">在恒速阶段</param>
        bool GetAngle(double a, DateTime dt, out double angle0, out double angle1, out Misc.DIRECTION direction, out int rotationCnt, out bool inCV)
        {
            angle0 = 0;
            angle1 = 0;
            direction = Misc.DIRECTION.FORWARD;
            rotationCnt = 0;
            inCV = true;
            if (a > 180 || a < 0)
                return false;

            //压扁对面的角度
            double a_other = 360 - a;
            double angle;


            //LimitListSearch(dt, true, out angle, out direction, out rotationCnt, out inCV);
            //不修改旋转速度!!!!!!!!!!!!
            LimitListSearch(dt, false, out angle, out direction, out rotationCnt, out inCV);


            a += angle;
            if (a >= 360)
                a -= 360;
            else if (a < 0)
                a += 360;

            a_other += angle;
            if (a_other >= 360)
                a_other -= 360;
            else if (a_other < 0)
                a_other += 360;
            angle0 = a;
            angle1 = a_other;
            return true;
        }



        public void Apply()
        {
            Save();
        }
        public class FilmInfoChangedEventArgs : EventArgs
        {
            public FilmInfoChangedEventArgs(DateTime time)
            {
                Time = time;
            }
            /// <summary>
            /// 开始时间点
            /// </summary>
            public DateTime Time
            {
                get;
                set;
            }
        }

        public delegate void FilmInfoChangedEventHandler(object sender, FilmInfoChangedEventArgs e);
        public event FilmInfoChangedEventHandler FilmInfoChangedEvent;

        public delegate void ClearEventHandler(object sender);
        public event ClearEventHandler ClearEvent;


        /// <summary>
        /// 释放资源
        /// </summary>
        public void Dispose()
        {
            Clear();
        }

        #region INotifyPropertyChanged 成员

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion


        /// <summary>
        /// 加载历史数据
        /// </summary>
        /// <returns></returns>
        public bool Load()
        {
            LoadFilmLength3D();
            if (File.Exists(file_path))
            {
                try
                {
                    string json = File.ReadAllText(file_path);
                    var jsonDb = JsonConvert.DeserializeObject<BlowingDetectJsonDb>(json);
                    BlowingDetectJsonDb.Mapper.Map(jsonDb, this);
                    return true;
                }
                catch
                {
                    //异常,没有json 解码失败

                }
                return false;
            }
            else//只是为了版本兼容,以后就会删除
            {
                var jsonDb = FLY.Thick.Blowing.Server.Update.BlowingDetect.ToJsonDb();
                if (jsonDb == null)
                    return false;
                BlowingDetectJsonDb.Mapper.Map(jsonDb, this);
                return true;
            }
        }
        string file_path = "blowingdetect.json";
        /// <summary>
        /// 保存
        /// </summary>
        public void Save()
        {
            try
            {
                var jsonDb = BlowingDetectJsonDb.Mapper.Map<BlowingDetectJsonDb>(this);
                string json = JsonConvert.SerializeObject(jsonDb, Formatting.Indented);
                File.WriteAllText(file_path, json);
            }
            catch
            {
                //异常,没有json 编码失败

            }
        }



        string file_path_filmlength3d = "filmlength3d.json";

        void LoadFilmLength3D()
        {
            try
            {
                if (File.Exists(file_path_filmlength3d))
                {
                    string json = File.ReadAllText(file_path_filmlength3d);
                    mFilmLength3D = JsonConvert.DeserializeObject<List<double>>(json);
                }
            }
            catch
            {
                //conf = null;
                //异常,没有json 解码失败
                //return false;
            }
        }

        void SaveFilmLength3D()
        {
            try
            {
                File.WriteAllText(file_path_filmlength3d, JsonConvert.SerializeObject(this, Formatting.Indented));
            }
            catch
            {
                //异常,没有json 编码失败

            }
        }



        /// <summary>
        /// 获取立式旋转架的旋转角度与膜距离增量关系
        /// </summary>
        /// <param name="asyncDelegate"></param>
        /// <param name="asyncContext"></param>
        public void GetFilmLength3D(AsyncCBHandler asyncDelegate, object asyncContext)
        {
            GetFilmLength3DReponse p = new GetFilmLength3DReponse();

            p.datas = mFilmLength3D;

            asyncDelegate(asyncContext, p);
        }

        /// <summary>
        /// 设置 立式旋转架 的 膜距离增量
        /// </summary>
        /// <param name="data"></param>
        public void SetFilmLength3D(List<double> data)
        {
            mFilmLength3D = data;
            SaveFilmLength3D();
        }

        /// <summary>
        /// 获取 信号列表
        /// </summary>
        /// <param name="asyncDelegate">retdata = GetSignListReponse</param>
        /// <param name="asyncContext"></param>
        public void GetSignList(AsyncCBHandler asyncDelegate, object asyncContext)
        {
            GetSignListReponse p = new GetSignListReponse();

            p.datas = mSignList;

            asyncDelegate(asyncContext, p);
        }
        /// <summary>
        /// 获取 信号列表
        /// </summary>
        /// <param name="begin">开始时间</param>
        /// <param name="asyncDelegate">retdata = GetSignListReponse</param>
        /// <param name="asyncContext"></param>
        public void GetSignList(DateTime begin, AsyncCBHandler asyncDelegate, object asyncContext)
        {
            GetSignListReponse p = new GetSignListReponse();

            p.datas = mSignList.FindAll(r => r.Time >= begin);

            asyncDelegate(asyncContext, p);
        }

        /// <summary>
        /// 获取 辊信号列表
        /// </summary>
        /// <param name="begin">开始时间</param>
        /// <param name="asyncDelegate">retdata = GetRollListReponse</param>
        /// <param name="asyncContext"></param>
        public void GetRollList(DateTime begin, AsyncCBHandler asyncDelegate, object asyncContext)
        {
            var p = new GetRollListReponse();

            p.datas = mRollList.FindAll(r => r.dt >= begin);

            asyncDelegate(asyncContext, p);
        }

        /// <summary>
        /// 获取 转向信号列表
        /// </summary>
        /// <param name="begin">开始时间</param>
        /// <param name="asyncDelegate">retdata = GetRollListReponse</param>
        /// <param name="asyncContext"></param>
        public void GetLimitList(DateTime begin, AsyncCBHandler asyncDelegate, object asyncContext)
        {
            var p = new GetLimitListReponse();

            p.datas = mLimitList.FindAll(r =>
            {
                return (r.dt_begin >= begin) || (r.dt_end >= begin);
            });
            asyncDelegate(asyncContext, p);
        }
    }

    public class BlowingDetectJsonDb
    {
        public static Mapper Mapper { get; } = new AutoMapper.Mapper(new MapperConfiguration(c => {
            c.CreateMap<BlowingDetect, BlowingDetectJsonDb>().ReverseMap();
        }));

        /// <summary>
        /// 离开限位 到 撞下一个限位 的 旋转架转动总角度 单位°
        /// </summary>
        public double RAngle;


        /// <summary>
        /// 人字架 旋转1周 设置 时间。 
        /// 刚开机时,RenZiJiaPeriod = DefaultRPeriod;
        /// 异常时,RenZiJiaPeriod = DefaultRPeriod;
        /// </summary>
        public TimeSpan DefaultRPeriod;


        /// <summary>
        /// 信号0 撞2次。 这个能自动设置。 两个撞同一信号 间隔30秒内,算一个换向信号。 
        /// 当为2次撞时:  StartTime = 第1次撞的StartTime, EndTime = 第2次撞的EndTime
        /// </summary>
        public bool IsSign0Double;


        /// <summary>
        /// 信号1 撞2次。 这个能自动设置。 两个撞同一信号 间隔30秒内,算一个换向信号。 
        /// 当为2次撞时:  StartTime = 第1次撞的StartTime, EndTime = 第2次撞的EndTime
        /// </summary>
        public bool IsSign1Double;

        /// <summary>
        /// 信号模式, 短信号模式(只撞一下),长信号模式(只要是转着,信号就长亮,直到换向)
        /// </summary>
        public BlowingSignType SignType = BlowingSignType.Short;




        /// <summary>
        /// 撞限位 到 离开限位 的时间, 需要初始值,以后测量出来的
        /// </summary>
        public TimeSpan LimitSignTime;

        /// <summary>
        /// 人字架到测厚仪膜长 单位m
        /// </summary>
        public double FilmLength;

        /// <summary>
        /// 辊周长,单位mm
        /// </summary>
        public double RollPerimeter;

        /// <summary>
        /// 转向信号100ms 滤波
        /// </summary>
        public bool IsSignFilter;


        /// <summary>
        /// 缺少信号模式
        /// 正反信号线接得不好,有时候会丢失了。 
        /// 当前的旋转时间是上一次的1.1倍时,在以前的位置增加信号!!!!
        /// 当有连续两次相同的信号,且信号差刚好是旋转时间的1.9~2.1倍时,证明真的是缺少信号
        /// </summary>
        public bool IsLackSignMode;

        /// <summary>
        /// 旋转架为立式,测厚仪安装在二牵引前面,膜距离 与 旋转角度 一起变化, 旋转角度越大 膜距离越大
        /// </summary>
        public bool Is3D;


    }


}

namespace FLY.Thick.Blowing.Server.Update 
{
    public class BlowingDetect : ISaveToXml 
    {

        public static Mapper Mapper { get; } = new AutoMapper.Mapper(new MapperConfiguration(c => {
            c.CreateMap<BlowingDetect, BlowingDetectJsonDb>();
        }));

        #region 参数

        /// <summary>
        /// 离开限位 到 撞下一个限位 的 旋转架转动总角度 单位°
        /// </summary>
        public double RAngle { get; set; }


        /// <summary>
        /// 人字架 旋转1周 设置 时间。 
        /// 刚开机时,RenZiJiaPeriod = DefaultRPeriod;
        /// 异常时,RenZiJiaPeriod = DefaultRPeriod;
        /// </summary>
        public TimeSpan DefaultRPeriod { get; set; }


        /// <summary>
        /// 信号0 撞2次。 这个能自动设置。 两个撞同一信号 间隔30秒内,算一个换向信号。 
        /// 当为2次撞时:  StartTime = 第1次撞的StartTime, EndTime = 第2次撞的EndTime
        /// </summary>
        public bool IsSign0Double { get; set; }


        /// <summary>
        /// 信号1 撞2次。 这个能自动设置。 两个撞同一信号 间隔30秒内,算一个换向信号。 
        /// 当为2次撞时:  StartTime = 第1次撞的StartTime, EndTime = 第2次撞的EndTime
        /// </summary>
        public bool IsSign1Double { get; set; }

        /// <summary>
        /// 信号模式, 短信号模式(只撞一下),长信号模式(只要是转着,信号就长亮,直到换向)
        /// </summary>
        public BlowingSignType SignType { get; set; } = BlowingSignType.Short;




        /// <summary>
        /// 撞限位 到 离开限位 的时间, 需要初始值,以后测量出来的
        /// </summary>
        public TimeSpan LimitSignTime { get; set; }

        /// <summary>
        /// 人字架到测厚仪膜长 单位m
        /// </summary>
        public double FilmLength { get; set; }

        /// <summary>
        /// 辊周长,单位mm
        /// </summary>
        public double RollPerimeter { get; set; }

        /// <summary>
        /// 转向信号100ms 滤波
        /// </summary>
        public bool IsSignFilter { get; set; }


        /// <summary>
        /// 缺少信号模式
        /// 正反信号线接得不好,有时候会丢失了。 
        /// 当前的旋转时间是上一次的1.1倍时,在以前的位置增加信号!!!!
        /// 当有连续两次相同的信号,且信号差刚好是旋转时间的1.9~2.1倍时,证明真的是缺少信号
        /// </summary>
        public bool IsLackSignMode { get; set; }

        /// <summary>
        /// 旋转架为立式,测厚仪安装在二牵引前面,膜距离 与 旋转角度 一起变化, 旋转角度越大 膜距离越大
        /// </summary>
        public bool Is3D { get; set; }

        #endregion

        /// <summary>
        /// Misc.SaveToXmlHepler 使用,保存在 xml文件内的 属性名称
        /// </summary>
        /// <returns></returns>
        public string[] GetSavePropertyNames()
        {
            return new string[]{

                nameof(RAngle),
                nameof(LimitSignTime),
                nameof(FilmLength),
                nameof(RollPerimeter),
                nameof(DefaultRPeriod),
                nameof(IsSign0Double),
                nameof(IsSign1Double),
                nameof(SignType),
                nameof(IsSignFilter),
                nameof(IsLackSignMode),
                nameof(Is3D)
            };
        }

        public static BlowingDetectJsonDb ToJsonDb()
        {
            if (File.Exists("blowingdetect.xml"))
            {//只是为了版本兼容,以后就会删除

                BlowingDetect bDetect = new BlowingDetect();

                Misc.SaveToXmlHepler.Load("blowingdetect.xml", bDetect);
                return Mapper.Map<BlowingDetectJsonDb>(bDetect);
            }
            else
                return null;
        }
    }
}