using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.IO;
using Newtonsoft.Json;

namespace FLY.Simulation.Blowing
{
    
    [JsonObject(MemberSerialization.OptIn)]
    public class Blowing:INotifyPropertyChanged
    {
        #region 参数
        #region 旋转架
        /// <summary>
        /// 360° 连续旋转
        /// </summary>
        [JsonProperty]
        public bool Is360 { get; set; }
        /// <summary>
        /// 正向旋转
        /// </summary>
        public bool IsForw { get; set; }

        /// <summary>
        /// 旋转架复位中
        /// </summary>
        public bool IsOrg { get; set; }

        /// <summary>
        /// 旋转架启动中
        /// </summary>
        public bool IsRotationOn { get; set; }

        /// <summary>
        /// 牵引启动中
        /// </summary>
        public bool IsTractionOn { get; set; }

        /// <summary>
        /// 1圈总脉冲
        /// </summary>
        [JsonProperty]
        public double PosOfR { get; set; }


        /// <summary>
        /// 两个限位间的角度范围  != 旋转的总角度,限位值 °
        /// </summary>
        [JsonProperty]
        public double AngleRange { get; set; }

        /// <summary>
        /// 复位信号角度范围°
        /// </summary>
        [JsonProperty]
        public double OrgAngleRange { get; set; }
        /// <summary>
        /// 旋转的速度, min/R 1R = 360°
        /// </summary>
        [JsonProperty]
        public double AngleVelocity { get; set; }


        /// <summary>
        /// 加速时间 单位 s
        /// </summary>
        [JsonProperty]
        public double AngleAccTime { get; set; }

        /// <summary>
        /// 减速时间 单位 s
        /// </summary>
        [JsonProperty]
        public double AngleDecTime { get; set; }

        /// <summary>
        /// 换方向冷却时间 单位s
        /// </summary>
        [JsonProperty]
        public double AngleCoolTime { get; set; }
        #endregion


        #region 膜

        /// <summary>
        /// 膜泡压扁后总宽度,单位 脉冲
        /// </summary>
        public double FilmWidth { get; set; }

        /// <summary>
        /// 膜走带速度, m/min
        /// </summary>
        [JsonProperty]
        public double FilmVelocity { get; set; }

        /// <summary>
        /// 人字架到测厚仪的距离 23m
        /// </summary>
        [JsonProperty]
        public double FilmDistance { get; set; }

        /// <summary>
        /// 辊轴承,单位mm
        /// </summary>
        [JsonProperty]
        public double RollPerimeter { get; set; }

        /// <summary>
        /// 测辊信号,信号长,单位mm
        /// </summary>
        [JsonProperty]
        public double RollSignLength { get; set; }
        #endregion

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

        public bool IsShieldI9 { get; set; }


        #endregion
        #region 动态数据



        /// <summary>
        /// 当前人字架角度 °
        /// </summary>
        public double CurrAngle { get; private set; }
        /// <summary>
        /// 全局角度 °
        /// </summary>
        public double GlobalAngle { get; private set; }
        /// <summary>
        /// 当前速度,单位 °/s
        /// </summary>
        public double CurrAngleVelocity { get; private set; }
        /// <summary>
        /// 当前加速度, 单位 °/s^2
        /// </summary>
        public double CurrAngleAccV { get; private set; }


        /// <summary>
        /// 当前已经走到长度, 通过编码器获取 单位 m
        /// </summary>
        public double CurrFilmLength { get; private set; }

        #endregion
        public HMI mHMI;
        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()
        {

            //撞了限位后,限位信号一直在。。。。
            PosOfR = 50000;
            OrgAngleRange = 5;//复位信号角度°
            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


            ChannelCnt = 88;//加热棒数量
            Channel1stAngle = 100;//第1根加热棒对应角度

            if (!Load())
                Save();

            //整个膜片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(ChannelCnt);//88支加热棒
            mAirRing.Init(BeforeDatas, BeforeDatas2, 0);


            mAirRing2 = new AirRing(ChannelCnt);//88支加热棒;
            mAirRing2.Init(BeforeDatas2, AfterDatas, (int)(1000*Channel1stAngle/360));//加热偏移了 

            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;



            Is360 = false;

            IsForw = true;
            IsOrg = false;
            IsRotationOn = true;
            IsTractionOn = true;
        }

        void mAirRing2_AfterDatasUpdateEvent(ObservableCollection<int> obj)
        {
            NotifyPropertyChanged(nameof(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 == nameof(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 == nameof(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 true;
                    }
                    //找到了
                    angle = (mPositionAngleList[idx1].angle - mPositionAngleList[idx2].angle) * percent + mPositionAngleList[idx2].angle;
                    return true;
                }
            }
            angle = 0;
            return true;
        }

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

            return GetData(position, angle);

        }
        double GetValidAngle(double a) {
            while (a >= 360)
                a -= 360;
            while (a < 0)
                a += 360;
            return a;
        }
        /// <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 = GetValidAngle( a + angle );
            a_other = GetValidAngle(a_other + angle);

            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;


        /// <summary>
        /// 更新旋转
        /// </summary>
        /// <param name="now"></param>
        /// <param name="ts"></param>
        void UpdateAngleV(DateTime now, TimeSpan ts)
        {
            if (IsOrg) {
                IsRotationOn = false;

                UpdateAngleV_Org(now, ts);
                return;
            }

            if (IsRotationOn) {
                if (Is360)
                {
                    UpdateAngleV_360(now, ts);
                }
                else {
                    UpdateAngleV_ForwBackw(now, ts);
                }
            }
        }



        /// <summary>
        /// 正反向旋转
        /// </summary>
        /// <param name="now"></param>
        /// <param name="ts"></param>
        void UpdateAngleV_ForwBackw(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;
                                IsForw = false;
                            }
                            else {
                                IsForw = true;
                            }
                            CurrAngleAccV = anglev / 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;
            }
            double delta = angle - CurrAngle;
            GlobalAngle += delta;
            CurrAngle = angle;
        }


        /// <summary>
        /// 连续旋转
        /// </summary>
        /// <param name="now"></param>
        /// <param name="ts"></param>
        void UpdateAngleV_360(DateTime now, TimeSpan ts)
        {
            double anglev = 360.0 / (AngleVelocity * 60);
            if (IsForw)
                CurrAngleVelocity = anglev;
            else
                CurrAngleVelocity = -anglev;

            double angle = CurrAngle + CurrAngleVelocity * ts.TotalSeconds;
            double delta = angle - CurrAngle;
            GlobalAngle += delta;

            while (angle >= 360)
                angle -= 360;
            while (angle < 0)
                angle +=360;

            CurrAngle = angle;

        }

        /// <summary>
        /// 复位
        /// </summary>
        /// <param name="now"></param>
        /// <param name="ts"></param>
        void UpdateAngleV_Org(DateTime now, TimeSpan ts)
        {
            double anglev = 360.0 / (AngleVelocity * 60);

            if (CurrAngle > 0){
                CurrAngleVelocity = -anglev;
            }
            else if (CurrAngle < 0) 
            { 
                CurrAngleVelocity = anglev;
            }
            else {
                CurrAngleVelocity = 0;
                IsOrg = false;
                return;
            }

            double angle = CurrAngle + CurrAngleVelocity * ts.TotalSeconds;
            double delta = angle - CurrAngle;

            if (((CurrAngle > 0) && (angle <= 0))||
                ((CurrAngle < 0) && (angle >= 0)))
            {
                delta = 0 - CurrAngle;
                CurrAngle = 0;
                CurrAngleVelocity = 0;
                IsOrg = false;
                return;
            }
            GlobalAngle += delta;
            CurrAngle = angle;
        }
        
        
        
        /// <summary>
        /// 驱动器,根据时间,更新angle,
        /// </summary>
        /// <param name="now"></param>
        void UpdatePositionAndAngle(DateTime now)
        {
            if (mDTLast == DateTime.MinValue)
            {
                double position = FilmDistance;
                DateTime time = now;
                while (position > 0)
                {
                    mPositionAngleList.Insert(0,
                        new PositionAngleInfo()
                        {
                            dt = time,
                            position = position,
                            angle = 0
                        });
                    TimeSpan ts1 = TimeSpan.FromMilliseconds(10);
                    time -= ts1;
                    position -= FilmVelocity * ts1.TotalMinutes;
                }

                mDTLast = now;
                CurrFilmLength = FilmDistance;
                CurrAngle = 0;//在中间

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


                return;
            }

            DateTime dt = now;

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

            mDTLast = dt;
            //线速度。。。。



            if(IsTractionOn)//启动牵引
                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 CheckOrg() 
        {
            if (CurrAngle <= OrgAngleRange / 2 && CurrAngle >= -OrgAngleRange / 2)
                return true;
            else
                return false;
        }
        public bool CheckLimit1() 
        {
            if (Is360)
            {
                if (CurrAngleVelocity < 0)
                    return true;
                else
                    return false;
            }
            else
            {
                if (CurrAngle >= AngleRange / 2)
                    return true;
                else
                    return false;
            }
        }
        public bool CheckLimit0()
        {
            if (Is360)
            {
                if (CurrAngleVelocity > 0)
                    return true;
                else
                    return false;
            }
            else
            {
                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;

        private string param_path = "simulation_blowing.json";
        public void Save() 
        {
            string json = Newtonsoft.Json.JsonConvert.SerializeObject(this, Formatting.Indented);
            File.WriteAllText(param_path, json);

        }
        bool Load() 
        {
            if (!File.Exists(param_path))
                return false;
            try
            {
                string json = File.ReadAllText(param_path);

                Newtonsoft.Json.JsonConvert.PopulateObject(json, this);
                return true;
            }
            catch {

                return false;
            }
        }
    }
    public class FilmData
    {
        public double angle1;
        public double angle2;
        public int data1;
        public int data2;
    }


}