using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Collections.ObjectModel;

namespace FLY.Simulation.Blowing
{
    
    /// <summary>
    /// 吹膜模拟
    /// 1.加载数据
    /// </summary>
    public class Blowing:INotifyPropertyChanged
    {
        #region 参数
        #region 旋转架
        
        /// <summary>
        /// 两个限位间的角度范围  != 旋转的总角度,限位值 °
        /// </summary>
        public double AngleRange;


        /// <summary>
        /// 旋转的速度， min/R 1R = 360°
        /// </summary>
        public double AngleVelocity { get; set; }


        /// <summary>
        /// 加速时间 单位 s
        /// </summary>
        public double AngleAccTime;
        /// <summary>
        /// 减速时间 单位 s
        /// </summary>
        public double AngleDecTime;
        /// <summary>
        /// 换方向冷却时间 单位s
        /// </summary>
        public double AngleCoolTime;
        #endregion


        #region 膜
        private double filmwidth;
        /// <summary>
        /// 膜泡压扁后总宽度，单位m
        /// </summary>
        public double FilmWidth { get; set; }







        /// <summary>
        /// 膜走带速度， m/min
        /// </summary>
        public double FilmVelocity { get; set; }

        /// <summary>
        /// 人字架到测厚仪的距离 23m
        /// </summary>
        public double FilmDistance;

        /// <summary>
        /// 辊轴承，单位mm
        /// </summary>
        public double RollPerimeter;

        /// <summary>
        /// 测辊信号，信号长，单位mm
        /// </summary>
        public double RollSignLength;
        #endregion

        //#region 风环
        ///// <summary>
        ///// 加热棒数量
        ///// </summary>
        //public int ChannelCnt;
        ///// <summary>
        ///// 加热功率， 1~100
        ///// </summary>
        //public int[] Heats;
        ///// <summary>
        ///// 第1根加热棒对应角度
        ///// </summary>
        //public int Channel1stAngle;
        ///// <summary>
        ///// 温度影响厚度变化比例， 1厚度，对应多少加热
        ///// </summary>
        //public int Factor;//
        //#endregion

        public bool IsShieldI9 { get; set; }


        #endregion
        #region 动态数据
        /// <summary>
        /// 当前人字架角度 °
        /// </summary>
        public double CurrAngle { get; set; }
        /// <summary>
        /// 当前速度，单位 °/s
        /// </summary>
        public double CurrAngleVelocity;
        /// <summary>
        /// 当前加速度, 单位 °/s^2
        /// </summary>
        public double CurrAngleAccV;


        /// <summary>
        /// 当前已经走到长度， 通过编码器获取 单位 m
        /// </summary>
        public double CurrFilmLength;

        #endregion
        public HMI mHMI;
        private int avg;
        public int Avg { get; set; }
        /// <summary>
        /// 原始数据
        /// </summary>
        public ObservableCollection<int> BeforeDatas = new ObservableCollection<int>();
        /// <summary>
        /// 系统扰动后 数据
        /// </summary>
        public ObservableCollection<int> BeforeDatas2 = new ObservableCollection<int>();
        /// <summary>
        /// 系统扰动后 再 加热数据
        /// </summary>
        public ObservableCollection<int> AfterDatas = new ObservableCollection<int>();




        class PositionAngleInfo
        {
            public DateTime dt;
            public double position;
            public double angle;
        }



        /// <summary>
        /// 10ms 1个记录点
        /// </summary>
        List<PositionAngleInfo> mPositionAngleList = new List<PositionAngleInfo>();

        int ListCap = 30000;//300s

        public Blowing()
        {

            //撞了限位后，限位信号一直在。。。。

            AngleRange = 350;//两个限位间的角度范围350°
            AngleVelocity = 6;//出限位 到 撞另一个限位的时间 8min/ 360°
            AngleAccTime = 1;//加速时间1s
            AngleDecTime = 5;//减速时间5s
            AngleCoolTime = 5;//正反转延迟时间5s 
            
            FilmWidth = 2.3;//膜宽2.3m
    
            FilmVelocity = 20;//线速度m/min
            FilmDistance = 26;//膜距离m


            RollPerimeter = 314;//辊周长mm
            RollSignLength = 30;//辊信号长度mm

            //整个膜片1000个数据
            int[] datas = OrgData.GetData(1000);
            for(int i=0;i<1000;i++)
            {
                BeforeDatas.Add(datas[i]);//原始数据
                BeforeDatas2.Add(datas[i]);//系统扰动后数据
                AfterDatas.Add(datas[i]);//系统扰动后 再 加热数据
            }

            mAirRing = new AirRing(88);//44支加热棒
            mAirRing.Init(BeforeDatas, BeforeDatas2, 0);


            mAirRing2 = new AirRing(88);//44支加热棒;
            mAirRing2.Init(BeforeDatas2, AfterDatas, 400);//加热偏移了 400个数据， 总数是1000

            mAirRing2.AfterDatasUpdateEvent += new Action<ObservableCollection<int>>(mAirRing2_AfterDatasUpdateEvent);
            Avg = (int)(BeforeDatas.Average());//原始平均值

            TestPos = 500;
            UpdateTestHeat();

            //AfterDatas.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(AfterDatas_CollectionChanged);
            PropertyChanged += new PropertyChangedEventHandler(Blowing_PropertyChanged);

            mHMI = new HMI();
            mHMI.mAirRing = mAirRing2;
        }

        void mAirRing2_AfterDatasUpdateEvent(ObservableCollection<int> obj)
        {
            NotifyPropertyChanged("AfterDatas");
        }
        

        public int TestPos { get; set; }

        public int TestHeat { get; set; }

        public void Test()
        {
            int idx = mAirRing.ChannelCnt * TestPos / BeforeDatas.Count();

            int offset = TestHeat - mAirRing.Heats[idx];

            mAirRing.Test(idx, offset);

            mAirRing2.HeatApply();
        }
        
        public class AirRing 
        {
            /// <summary>
            /// 加热通过
            /// </summary>
            public int ChannelCnt;
            /// <summary>
            /// 加热量， 数据量为 ChannelCnt
            /// </summary>
            public int[] Heats;
            /// <summary>
            /// 第1个加热棒，对应的 数据序号， 数据量为 1000， BeforeData.Count
            /// </summary>
            public int Channel1stIndex;
            /// <summary>
            /// 加热 对 厚度的影响
            /// </summary>
            public double Factor;

            //需要别人赋值
            /// <summary>
            /// 原始数据！！ 数据量为1000
            /// </summary>
            public ObservableCollection<int> BeforeDatas;
            /// <summary>
            /// 加热后的数据！！！！ 数据量为BeforeDatas.Count
            /// </summary>
            public ObservableCollection<int> AfterDatas;


            double[] p;// 一个凸起来的数组  0 1 4 9 16 9 4 1 0
            void p_init()
            {
                int size = ChannelCnt / 10;
                p = new double[size];
                for (int i = 0; i < size; i++)
                {
                    int index1 = i;
                    int index2 = size - 1 - i;
                    double d = i * i;
                    p[index1] = d;
                    p[index2] = d;
                    if (Math.Abs(index2-index1)<=1)
                        break;
                }
                double sum_p = p.Max();
                for (int i = 0; i < size; i++)
                {
                    p[i] = p[i] / sum_p;
                }
            }
            public AirRing(int channelcnt)
            {
                SetChannelCnt(channelcnt);
                p_init();
                Factor = 5;
            }
            public void Init(ObservableCollection<int> before_datas, ObservableCollection<int> after_datas, int channel1stIndex)
            {
                BeforeDatas = before_datas;
                AfterDatas = after_datas;
                Channel1stIndex = channel1stIndex;

            }
            public void SetChannelCnt(int channelcnt)
            {
                ChannelCnt = channelcnt;
                Heats = new int[ChannelCnt];
                Array.Clear(Heats, 0, ChannelCnt);
            }
            public event Action<ObservableCollection<int>> AfterDatasUpdateEvent;
            public void HeatApply() 
            {
                int boltcnt = BeforeDatas.Count();
                double b_c = (double)(boltcnt)/ChannelCnt;
                int b = Channel1stIndex;
                double beforeavg = BeforeDatas.Average();
                int[] datas = new int[boltcnt];
                for (int i = 0; i < ChannelCnt; i++) 
                {
                    int heat = Heats[i];
                    int e = (int)((i+1) * b_c)+ Channel1stIndex;

                    if (e >= boltcnt)
                        e -= boltcnt;
                    else if (e < 0)
                        e += boltcnt;
                        
                    int j=b;
                    while (j != e)
                    {
                        datas[j] = (int)(BeforeDatas[j] - beforeavg * heat / Factor/100);

                        j++;
                        if (j >= boltcnt)
                            j -= boltcnt;
                    }
                    b = e;
                }
                double afteravg = datas.Average();

                for (int j = 0; j < boltcnt; j++)
                {
                    AfterDatas[j] = (int)(beforeavg * datas[j] / afteravg);
                }
                if (AfterDatasUpdateEvent != null)
                {
                    AfterDatasUpdateEvent(AfterDatas);
                }
            }
            
            public void Test(int idx,int offset) 
            {
                idx -= p.Count() / 2;
                if (idx < 0)
                    idx += ChannelCnt;
                else if (idx >= ChannelCnt)
                    idx -= ChannelCnt;

                for (int i = 0; i < p.Count(); i++) 
                {
                    int index = idx+i;
                    
                    if (index < 0)
                        index += ChannelCnt;
                    else if (index>= ChannelCnt)
                        index -= ChannelCnt;


                    int heat = Heats[index] + (int)(offset * p[i]);
                    if (heat > 100)
                        heat = 100;
                    else if (heat < 0)
                        heat = 0;
                    Heats[index] = heat;
                }
                HeatApply();
            }
        }
        /// <summary>
        /// 1级加热, 模拟系统扰动
        /// </summary>
        private AirRing mAirRing;
        /// <summary>
        /// 2级加热, 通过HMI 控制的加热
        /// </summary>
        public AirRing mAirRing2;

        void Blowing_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "Avg") 
            {
                int avg = (int)(BeforeDatas.Average());
                int offset = Avg - avg;
                if (offset != 0)
                {
                    for (int i = 0; i < BeforeDatas.Count(); i++)
                    {
                        BeforeDatas[i] += offset;
                    }
                }
            }
            else if (e.PropertyName == "TestPos")
            {
                UpdateTestHeat();
            }
        }
        void UpdateTestHeat() 
        {
            int idx = mAirRing.ChannelCnt * TestPos / BeforeDatas.Count();
                TestHeat = mAirRing.Heats[idx];
        }


        bool SearchAngleFromPositionList(double position, out double angle)
        {
            for (int i = 1; i < mPositionAngleList.Count; i++)
            {
                int idx1 = i;
                int idx2 = i - 1;
                if ((mPositionAngleList[idx1].position >= position))
                {

                    double percent = (position - mPositionAngleList[idx2].position) / (mPositionAngleList[idx1].position - mPositionAngleList[idx2].position);
                    if (percent < 0)
                    {
                        angle = 0;
                        return false;
                    }
                    //找到了
                    angle = (mPositionAngleList[idx1].angle - mPositionAngleList[idx2].angle) * percent + mPositionAngleList[idx2].angle;
                    return true;
                }
            }
            angle = 0;
            return false;
        }

        public FilmData GetData(double position)
        {
            double ago_filmlength = CurrFilmLength - FilmDistance;
            double angle;
            //从膜位置列表找时间
            if (!SearchAngleFromPositionList(ago_filmlength, out angle))
                return null;

            return GetData(position, angle);

        }

        /// <summary>
        /// 获取膜泡数据
        /// </summary>
        /// <param name="position">相对于膜泡位置，横向</param>
        /// <param name="angle">人字架角度</param>
        /// <returns></returns>
        FilmData GetData(double position, double angle)
        {
            if ((position < 0) || (position > FilmWidth))
            {
                return null;
            }
            //与0°的偏移
            double a = position / FilmWidth * 180;
            //压扁对面的角度
            double a_other = 360 - a;

            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;

            int dat_idx1 = (int)(a / 360 * AfterDatas.Count());
            int dat_idx2 = (int)(a_other / 360 * AfterDatas.Count());

            FilmData fd = new FilmData()
            {
                angle1 = a,
                angle2 = a_other,
                data1 = AfterDatas[dat_idx1],
                data2 = AfterDatas[dat_idx2]
            };
            return fd;
        }

        /// <summary>
        /// 1.28ms 1次
        /// </summary>
        public void OnPoll(DateTime now)
        {
            UpdatePositionAndAngle(now);
        }


        enum STATE 
        {
            /// <summary>
            /// 加速
            /// </summary>
            Acc,
            /// <summary>
            /// 减速
            /// </summary>
            Dec,
            /// <summary>
            /// 恒速
            /// </summary>
            Constant,
            /// <summary>
            /// 方向交换，冷却期
            /// </summary>
            Wrap
        }
        STATE mState;
        DateTime mDTLast = DateTime.MinValue;
        /// <summary>
        /// 停下来的时间
        /// </summary>
        DateTime mDTWrap = DateTime.MinValue;

        void UpdateAngleV(DateTime now, TimeSpan ts) 
        {
            double angle = CurrAngle + CurrAngleVelocity * ts.TotalSeconds;
            //旋转
            switch (mState)
            {
                case STATE.Constant:
                    if (((CurrAngleVelocity > 0) && (angle >= AngleRange / 2)) ||
                        ((CurrAngleVelocity < 0) && (angle <= (-AngleRange / 2))))
                    {
                        mState = STATE.Dec;
                        double dv = 0 - CurrAngleVelocity;
                        CurrAngleAccV = dv / AngleDecTime;
                    } break;
                case STATE.Dec:
                    {
                        bool isForw = false;
                        if (CurrAngleVelocity > 0)
                            isForw = true;
                        CurrAngleVelocity += CurrAngleAccV * ts.TotalSeconds;
                        if (((isForw) && (CurrAngleVelocity < 0)) ||
                            ((!isForw) && (CurrAngleVelocity > 0)))
                        {
                            CurrAngleVelocity = 0;
                            mState = STATE.Wrap;
                            mDTWrap = now;
                        }
                    } break;
                case STATE.Wrap:
                    {
                        if ((now - mDTWrap).TotalSeconds >= AngleCoolTime)
                        {
                            //冷静完
                            mState = STATE.Acc;
                            double anglev = 360.0 / (AngleVelocity * 60);
                            if (angle >= AngleRange / 2)
                            {
                                //反转
                                anglev = -anglev;
                            }
                            double dv = anglev - CurrAngleVelocity;
                            CurrAngleAccV = dv / AngleAccTime;
                        }
                    } break;
                case STATE.Acc:
                    {
                        CurrAngleVelocity += CurrAngleAccV * ts.TotalSeconds;

                        double anglev = 360.0 / (AngleVelocity * 60);
                        if (CurrAngleAccV > 0)
                        {
                            if (CurrAngleVelocity >= anglev)
                            {
                                CurrAngleVelocity = anglev;
                                mState = STATE.Constant;
                            }
                        }
                        else
                        {
                            if (CurrAngleVelocity <= -anglev)
                            {
                                CurrAngleVelocity = -anglev;
                                mState = STATE.Constant;
                            }
                        }
                    } break;
            }

            CurrAngle = angle;
        }
        void UpdatePositionAndAngle(DateTime now)
        {
            if (mDTLast == DateTime.MinValue)
            {
                mDTLast = now;
                CurrFilmLength = 0;
                CurrAngle = 0;//在中间

                mState = STATE.Constant;
                double anglev = 360.0 / (AngleVelocity * 60);
                CurrAngleVelocity = anglev;

                mPositionAngleList.Add(
                    new PositionAngleInfo()
                    {
                        dt = mDTLast,
                        position = CurrFilmLength,
                        angle = CurrAngle
                    });
                return;
            }

            DateTime dt = now;

            TimeSpan ts = dt.Subtract(mDTLast);
            if (ts.TotalMilliseconds < 10)
                return;

            mDTLast = dt;
            //线速度。。。。
            CurrFilmLength += FilmVelocity * ts.TotalMinutes;
            UpdateAngleV(dt, ts);


            mPositionAngleList.Add(
                new PositionAngleInfo()
                {
                    dt = mDTLast,
                    angle = CurrAngle,
                    position = CurrFilmLength
                });

            if (mPositionAngleList.Count > ListCap)
                mPositionAngleList.RemoveAt(0);
        }

        public bool CheckLimit1() 
        {
            if (CurrAngle >= AngleRange / 2)
                return true;
            else
                return false;
        }
        public bool CheckLimit0()
        {
            if (CurrAngle <= (-AngleRange / 2))
                return true;
            else
                return false;
        }
        public bool CheckRoll() 
        {
            int rollcnt = (int)(CurrFilmLength*1000 / RollPerimeter);
            double p = CurrFilmLength*1000 - rollcnt * RollPerimeter;
            if (p <= RollSignLength)
                return true;
            else
                return false;
        }
        protected void NotifyPropertyChanged(string propertyname) 
        {
            if (PropertyChanged != null) 
            {
                PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyname));
            }
        }
        public event PropertyChangedEventHandler PropertyChanged;
    }
    public class FilmData
    {
        public double angle1;
        public double angle2;
        public int data1;
        public int data2;
    }
}
