using FLY.Thick.FilmCasting.Common;
using FLY.Thick.FilmCasting.IService;
using FLY.Thick.FilmCasting.Server.Model;
using FObjBase;
using LiveCharts;
using LiveCharts.Configurations;
using LiveCharts.Wpf;
using Misc;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Threading;

namespace FLY.Thick.FilmCasting.UI.UiModule
{
    public class ScanGraphVm : INotifyPropertyChanged
    {
        const int MARKNO_TEXTUP = 1;
        
        /// <summary>
        /// 自动切换为 DefaultNumber 倒计时 初始值
        /// </summary>
        const int AutoKeepTotalSec = 60;

        /// <summary>
        /// Y轴%显示，最小的中值
        /// </summary>
        const int MIN_PERCENT_TARGET = 5;
        /// <summary>
        /// Y轴最小公差范围
        /// </summary>
        const int MIN_TOLERANCE = 1;
        #region 图表控制
        public double YMax { get; set; } = double.NaN;
        public double YMin { get; set; } = double.NaN;
        public double Target { get; set; } = double.NaN;
        public double ToleranceYMax { get; set; } = double.NaN;
        public double ToleranceYMin { get; set; } = double.NaN;
        public double Tolerance2YMax { get; set; } = double.NaN;
        public double Tolerance2YMin { get; set; } = double.NaN;
        public double XMax { get; set; } = double.NaN;
        public double XMin { get; set; } = double.NaN;

        public int XStep { get; set; } = 10;

        public Func<double, string> YFormatter { get; set; }
        public Func<double, string> XFormatter { get; set; }

        public SeriesCollection Series { get; set; }

        #endregion



        #region 界面统计值
        /// <summary>
        /// 平均值
        /// </summary>
        public double Average { get; protected set; } = double.NaN;

        public double Sigma2 { get; protected set; } = double.NaN;
        /// <summary>
        /// 最大值
        /// </summary>
        public double Max { get; protected set; } = double.NaN;
        /// <summary>
        /// 最小值
        /// </summary>
        public double Min { get; protected set; } = double.NaN;

        public string Sigma2Text { get; protected set; }
        public string MaxText { get; protected set; }
        public string MinText { get; protected set; }

        /// <summary>
        /// 混合数
        /// </summary>
        public int Mix { get; protected set; } = 2;

        /// <summary>
        /// 测量时间
        /// </summary>
        public DateTime Time { get; protected set; }

        /// <summary>
        /// 测量时间
        /// </summary>
        public DateTime EndTime { get; protected set; }
        /// <summary>
        /// 数据库中的序号
        /// </summary>
        public long Id { get; protected set; }

        public double Velocity { get; protected set; }
        public double Position { get; protected set; }

        /// <summary>
        /// 膜宽 mm
        /// </summary>
        public double FilmWidth { get; protected set; }

        /// <summary>
        /// 只要 Number被改变,且不为0, AutoKeepNewestTimeRemaining就会被设为10s.
        /// 当 AutoKeepNewestTimeRemaining = 0, Number就会被设置为0
        /// </summary>
        public int AutoKeepNewestTimeRemaining { get; protected set; }

        /// <summary>
        /// Number == graphparam.Number
        /// </summary>
        public bool IsNewest { get; protected set; } = true;

        /// <summary>
        /// 锁上, Number 不会 自动变回 DefaultNumber，且 Number = Id
        /// </summary>
        public bool IsLocked { get; set; }

        /// <summary>
        /// 记录点， 与 graphparam.Number 分离
        /// </summary>
        public long Number { get; set; }
        /// <summary>
        /// 记录点 变化步进
        /// </summary>
        public int NumberStep { get; set; } = 1;
        #endregion

        protected List<Brush> AreaColors;

        //最大只能显示100个数据，大于100， 都会被/2
        protected ChartValues<double> Values = new ChartValues<double>();

        //double 转 XY
        protected object Mapper;

        protected FilmCastingProfileParam profileParam;
        protected ScanGraphItemParam graphparam;
        protected IBulkDbService bulkDb;
        protected Lc_ScanData scanData;
        protected ParamDictionary paramDictionary;
        protected DispatcherTimer timer;
        public ScanGraphVm()
        {
            #region 与数据无关界面参数

            AreaColors = FLY.Thick.FilmCasting.UI.Themes.Colors.AreaColors;

            YFormatter = (y) =>
            {
                //只显示 Target,ToleranceYMax,ToleranceYMin,Tolerance2YMax,Tolerance2YMin
                string text = "";
                double target = Target;
                if (graphparam.IsPercent && target > MIN_PERCENT_TARGET)
                {
                    if (y == target)
                    {
                        text = y.ToString("F1");
                    }
                    else
                    {
                        double percent = 100.0 * (y - target) / target;
                        if (percent > 0)
                        {
                            text = $"+{Math.Abs(percent):F1}%";
                        }
                        else
                        {
                            text = $"-{Math.Abs(percent):F1}%";
                        }
                    }
                }
                else
                {
                    text = y.ToString("F1");
                }
                return $"{text,6}";
            };

            XFormatter = (x) =>
            {
                return $"{x + 1}";
            };

            Mapper = Mappers.Xy<double>()
                .X((value, index) =>
                {
                    return index;
                })
                .Y(value => value)
                .Fill((value) =>
                {
                    if ((value > Tolerance2YMax) || (value < Tolerance2YMin))
                    {
                        return AreaColors[2];
                    }
                    else if ((value > ToleranceYMax) || (value < ToleranceYMin))
                    {
                        return AreaColors[1];
                    }
                    else
                    {
                        return AreaColors[0];
                    }
                });
            #endregion

        }
        protected void UpdateSeriesType()
        {
            if (graphparam.IsLineMode)
            {
                LineSeries lineSeries_avg = new LineSeries
                {
                    Values = Values,
                    StrokeThickness = 5,
                    Stroke = new SolidColorBrush(System.Windows.Media.Colors.Black),
                    Fill = new SolidColorBrush(System.Windows.Media.Colors.Transparent),
                    PointGeometry = null,
                };
                lineSeries_avg.SetValue(System.Windows.Controls.Panel.ZIndexProperty, 10);

                Series = new SeriesCollection
                {
                    lineSeries_avg
                };
            }
            else
            {
                Series = new SeriesCollection
                {
                    new Column2Series
                    {
                        Values = Values,
                        Stroke = new SolidColorBrush(Colors.Black),
                        StrokeThickness = 1,
                        PointGeometry = null,
                        Configuration = Mapper
                    }
                };
                ((Series)Series[0]).SetBinding(Column2Series.YAxisCrossingProperty, new Binding(nameof(Target)) { Source = this });
            }

        }
        public void Init(
            ScanGraphItemParam graphparam,
            IBulkDbService bulkDb,
            ParamDictionary paramDictionary,
            FilmCastingProfileParam profileParam
            )
        {
            this.graphparam = graphparam;
            this.bulkDb = bulkDb;
            this.paramDictionary = paramDictionary;
            this.profileParam = profileParam;

            UpdateSeriesType();
            UpdateY();
            UpdateX();
            

            Values.Clear();



            bulkDb.PropertyChanged += BulkDb_PropertyChanged;
            graphparam.PropertyChanged += Graphparam_PropertyChanged;
            profileParam.PropertyChanged += ProfileParam_PropertyChanged;
            this.PropertyChanged += ScanGraphVm_PropertyChanged;


            init_autoKeep();

            Id = -1;
            UpdateId();

            Init2();
        }

        protected virtual void Init2()
        {
            paramDictionary.SetBinding(this, nameof(XStep), Base.UI.ParamDistItemKeys.XStep, XStep);
            
        }

        void init_autoKeep() {

            ResetNumber();

            timer = new DispatcherTimer();
            timer.Interval = TimeSpan.FromSeconds(1);
            timer.Tick += (s, e) =>
            {
                if (Number == graphparam.Number)
                {
                    timer.Stop();
                    return;
                }

                if (IsLocked) {
                    return;
                }

                if (AutoKeepNewestTimeRemaining > 0)
                    AutoKeepNewestTimeRemaining--;
                if (AutoKeepNewestTimeRemaining <= 0)
                {
                    ResetNumber();
                    timer.Stop();
                    return;
                }
            };
            this.graphparam.PropertyChanged += (s, e) =>
            {
                if (e.PropertyName == nameof(graphparam.Number))
                {
                    ResetNumber();
                    timer.IsEnabled = false;
                    IsNewest = true;
                    AutoKeepNewestTimeRemaining = AutoKeepTotalSec;
                }
            };
            this.PropertyChanged += (s, e) => {
                if (e.PropertyName == nameof(IsLocked)) {
                    if (Id >= 0)
                    {
                        Number = Id;
                    }
                }
                else if (e.PropertyName == nameof(Number))
                {
                    timer.IsEnabled = Number != graphparam.Number;
                    IsNewest = !timer.IsEnabled;
                    AutoKeepNewestTimeRemaining = AutoKeepTotalSec;
                }
            };
        }
        static string[] propertyname_updateMaxMinText = new string[] {
            nameof(Average),nameof(Max),nameof(Min),nameof(Sigma2)};

        private void ScanGraphVm_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {

            if (e.PropertyName == nameof(Target))//nameof(Average)
            {
                if (graphparam.IsPercent)
                {
                    NotifyPropertyChanged(nameof(YFormatter));
                }
            }
            else if (e.PropertyName == nameof(Number))
            {
                UpdateId();
            }
            

            if (propertyname_updateMaxMinText.Contains(e.PropertyName))
                UpdateMaxMinText();

        }

        private void ProfileParam_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == nameof(profileParam.Target))
            {
                UpdateY();
            }
            else if (e.PropertyName == nameof(profileParam.TolerancePercent))
            {
                UpdateY();
            }

        }

        private void BulkDb_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == nameof(bulkDb.LastScanDataId))
            {
                UpdateId();
            }
            else if (e.PropertyName == nameof(FObjBase.FObjServiceClient.IsConnected))
            {
                if ((bulkDb as FObjBase.FObjServiceClient).IsConnected)
                {
                    UpdateId();
                }
            }
        }

        private void Graphparam_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == nameof(graphparam.Mix))
            {
                UpdateId();
            }
            else if (e.PropertyName == nameof(graphparam.YRangePercent))
            {
                UpdateY();
            }
            else if (e.PropertyName == nameof(graphparam.IsPercent))
            {
                NotifyPropertyChanged(nameof(YFormatter));
                UpdateY();
                UpdateMaxMinText();

            }
            else if (e.PropertyName == nameof(graphparam.IsAutoTarget))
            {
                UpdateY();
            }
            else if (e.PropertyName == nameof(graphparam.IsLineMode))
            {
                UpdateSeriesType();
            }
        }



        void UpdateY()
        {
            //使用 profileParam 的参数显示
            UpdateY_profileParam();
        }
        void UpdateY_profileParam()
        {
            double target = this.profileParam.Target;

            if (graphparam.IsAutoTarget)
            {
                if (!double.IsNaN(Average))
                    target = Average;
            }

            double tolerance = this.profileParam.TolerancePercent * target;

            //确保没有负数，Y轴显示范围正确
            if (tolerance < MIN_TOLERANCE)
                tolerance = MIN_TOLERANCE;

            Target = target;
            ToleranceYMax = target + tolerance;
            ToleranceYMin = target - tolerance;
            Tolerance2YMax = target + tolerance * 2;
            Tolerance2YMin = target - tolerance * 2;

            YMax = target + tolerance * graphparam.YRangePercent;
            YMin = target - tolerance * graphparam.YRangePercent;
        }

        protected void UpdateX()
        {
            if (profileParam.ScanRange.IsValid) {
                XMin = profileParam.ScanRange.Begin - 1;
                XMax = profileParam.ScanRange.End + 1;
            }
        }

        void UpdateId()
        {
            Update4IdChanged();
        }

        void UpdateMaxMinText()
        {
            PollModule.Current.Poll_JustOnce(() =>
            {
                if (graphparam.IsPercent && (Average > MIN_PERCENT_TARGET))
                {
                    MaxText = $"+{((Max - Average) / Average) * 100.0:F1}%";
                    MinText = $"-{((Average - Min) / Average) * 100.0:F1}%";
                    Sigma2Text = $"{(Sigma2 / Average) * 100.0:F1}%";
                }
                else
                {
                    MaxText = $"{Max:F1}";
                    MinText = $"{Min:F1}";
                    Sigma2Text = $"{Sigma2:F1}";
                }
            }, this, MARKNO_TEXTUP);
        }

        protected virtual void UpdateAverage()
        {
            if (this.scanData == null)
            {
                Time = DateTime.MinValue;
                Id = -1;
                Position = -1;
                Velocity = -1;
                FilmWidth = -1;

                Average = double.NaN;
                Max = double.NaN;
                Min = double.NaN;
                Sigma2 = double.NaN;

                return;
            }

            Time = this.scanData.Time;
            EndTime = this.scanData.EndTime;
            Id = this.scanData.ID;
            Position = this.scanData.FilmPosition;
            Velocity = this.scanData.FilmVelocity;
            FilmWidth = this.scanData.FilmWidth;
            


            List<double> list = new List<double>();
            double[] values = this.scanData.Thicks;
            if (this.profileParam.DataRange.IsValid) {

                int beginIndex = this.profileParam.DataRange.Begin;
                int endIndex = this.profileParam.DataRange.End;

                for (int i = beginIndex; i <= endIndex; i++)
                {
                    if (i < 0)
                        i = 0;
                    if (i >= values.Count())
                        break;
                    double v = values[i];
                    if (!double.IsNaN(v))
                        list.Add(values[i]);
                }
            }

            if (list.Count() > 0)
            {
                Average = list.Average();
                Max = list.Max();
                Min = list.Min();
                Sigma2 = list.Sigma() * 2;
            }
            else
            {
                Average = double.NaN;
                Max = double.NaN;
                Min = double.NaN;
                Sigma2 = double.NaN;
            }
        }

        bool IsNeedToGetFrame() 
        {
            if (Mix != graphparam.Mix)
            {
                return true;
            }

            if(Number>0) 
            {
                //绝对位置模式
                if (Number != Id)
                    return true;
                else
                    return false;
            }
            //相对位置模式

            if (Number == Id - bulkDb.LastScanDataId)
            {
                //已经是需要查找的记录
                return false;
            }

            return true;
        }
        void Update4IdChanged()
        {
            if (!IsNeedToGetFrame())
                return;

            bulkDb.GetFrame(
                new Pack_GetFrameRequest() { Id = Number, Mix = graphparam.Mix },
                (object AsyncContext, object retData) =>
                {
                    Pack_GetFrameReponse reponse = retData as Pack_GetFrameReponse;
                    
                    this.scanData = reponse.scanData;
                    Mix = reponse.Request.Mix;

                    //清除全部数据
                    Values.Clear();

                    //更新平均值,它会引起 YFormatter 刷新
                    UpdateAverage();

                    //更新Target
                    UpdateY();

                    UpdateX();

                    //必须先更新Y轴, X轴资料，最后再画线！！！！！！！
                    //不然 YAxisCrossing 会有机会无效

                    if (scanData != null && scanData.Thicks != null)
                    {
                        Values.AddRange(scanData.Thicks);
                    }

                }, this);
        }


        public void PreView()
        {
            Number -= NumberStep;
        }

        public void NextView()
        {
            Number += NumberStep;
        }

        public void ChangeLock()
        {
            IsLocked = !IsLocked;
        }

        public void ResetNumber() 
        {
            Number = graphparam.Number;
        }
        protected void NotifyPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
        public event PropertyChangedEventHandler PropertyChanged;
    }
}
