using CommunityToolkit.Mvvm.Input;
using FLY.Thick.Base.IService;
using LiveCharts;
using LiveCharts.Configurations;
using LiveCharts.Wpf;
using MathNet.Numerics.LinearAlgebra.Double;
using Microsoft.Win32;
using Misc;
using OfficeOpenXml;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Data;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using Unity;

namespace FLY.Thick.Base.UI
{
    /// <summary>
    /// Page_ScanCorr.xaml 的交互逻辑
    /// </summary>
    public partial class PgScanCorr : Page
    {
        PgScanCorrVm viewModel;
        public PgScanCorr()
        {
            InitializeComponent();

            if (System.ComponentModel.LicenseManager.UsageMode != System.ComponentModel.LicenseUsageMode.Runtime)
                return;

            this.Loaded += Page_Loaded;
            this.Unloaded += Page_Unloaded;

        }

        private void Page_Unloaded(object sender, RoutedEventArgs e)
        {
            viewModel.DisposeBinding();
        }

        private void Page_Loaded(object sender, RoutedEventArgs e)
        {
            viewModel.SetBinding();
        }

        [InjectionMethod]
        public void Init(IUnityContainer container,
            IScanCorrService scanCorrService,
            IInitParamService initParamService,
            ITDGageService gageService
            )
        {
            viewModel = new PgScanCorrVm();
            viewModel.Init(scanCorrService, initParamService, gageService);
            this.DataContext = viewModel;

            container.BuildUp(mircoGage);
        }


        private void UIElement_OnMouseMove(object sender, MouseEventArgs e)
        {
            if (!isDown)
                return;

            var vm = viewModel;
            var chart = (LiveCharts.Wpf.CartesianChart)sender;


            //lets get where the mouse is at our chart
            var mouseCoordinate = e.GetPosition(chart);

            //now that we know where the mouse is, lets use
            //ConverToChartValues extension
            //it takes a point in pixes and scales it to our chart current scale/values
            var p = chart.ConvertToChartValues(mouseCoordinate);


            //var series = vm.Series[0];

            //var closetsPoint = series.ClosestPointTo(p.X, AxisOrientation.X);
            //if (closetsPoint == null)
            //    return;

            //pEnd = closetsPoint;
            pEnd = p;
            vm.Select(PgScanCorrVm.SelectType.Move, pBegin, pEnd);

        }

        private System.Windows.Point pBegin;
        private System.Windows.Point pEnd;
        private bool isDown = false;
        private void UIElement_MouseDown(object sender, MouseButtonEventArgs e)
        {
            isDown = true;
            var vm = viewModel;
            var chart = (LiveCharts.Wpf.CartesianChart)sender;


            var mouseCoordinate = e.GetPosition(chart);
            var p = chart.ConvertToChartValues(mouseCoordinate);
            pBegin = p;

            //var series = chart.Series[0];
            //ChartPoint closetsPoint = series.ClosestPointTo(p.X, AxisOrientation.X);
            //if (closetsPoint == null)
            //{
            //    isDown = false;
            //    return;
            //}
            //cpBegin = closetsPoint;

            vm.Select(PgScanCorrVm.SelectType.Click, pBegin, pBegin);

        }
        private void UIElement_MouseUp(object sender, MouseButtonEventArgs e)
        {
            isDown = false;
            var vm = viewModel;
            var series = vm.Series[0];
            var chart = (LiveCharts.Wpf.CartesianChart)sender;

            //lets get where the mouse is at our chart
            var mouseCoordinate = e.GetPosition(chart);

            //now that we know where the mouse is, lets use
            //ConverToChartValues extension
            //it takes a point in pixes and scales it to our chart current scale/values
            var p = chart.ConvertToChartValues(mouseCoordinate);

            //for X in this case we will only highlight the closest point.
            //lets use the already defined ClosestPointTo extension
            //it will return the closest ChartPoint to a value according to an axis.
            //here we get the closest point to p.X according to the X axis
            //var series = chart.Series[0];
            //var closetsPoint = series.ClosestPointTo(p.X, AxisOrientation.X);
            //if (closetsPoint == null)
            //    return;
            pEnd = p;
            vm.Select(PgScanCorrVm.SelectType.Cancel, pBegin, pEnd);
        }
    }

    public class PgScanCorrVm : INotifyPropertyChanged
    {

        #region 轴调节
        /// <summary>
        /// Y轴拖拉条 最大值
        /// </summary>
        public double YRangeSliderMax { get; set; } = 65535;

        /// <summary>
        /// Y轴拖拉条 最小值
        /// </summary>
        public double YRangeSliderMin { get; set; } = 0;
        #endregion


        #region 曲线
        public Func<double, string> XFormatter { get; private set; }
        public Func<double, string> YFormatter { get; private set; }

        public double XMin => 0;
        public double XMax => PosLength / PosOfGrid;

        private double ymax = 65535;
        /// <summary>
        /// Y轴 最大值
        /// </summary>
        public double YMax
        {
            get { return ymax; }
            set
            {
                if (value <= YMin)
                {
                    return;
                }
                if (value != ymax)
                    ymax = value;
            }
        }

        private double ymin = 0;
        /// <summary>
        /// Y轴 最小值
        /// </summary>
        public double YMin
        {
            get { return ymin; }
            set
            {
                if (value >= YMax)
                {
                    return;
                }
                if (value != ymin)
                    ymin = value;
            }
        }

        public SeriesCollection Series { get; } = new SeriesCollection();

        public class SeriesInfo : INotifyPropertyChanged
        {
            public string Name { get; set; }
            public Brush Color { get; set; }

            public ChartValues<double> Datas = new ChartValues<double>();

            public event PropertyChangedEventHandler PropertyChanged;
        }
        public SeriesInfo[] SeriesInfos { get; set; }

        ChartValues<XY> KeyPointDatas = new ChartValues<XY>();
        CartesianMapper<XY> Mapper { get; set; }

        #endregion

        /// <summary>
        /// 机架总长，脉冲
        /// </summary>
        public int PosLength { get; set; } = 8000;

        /// <summary>
        /// 1个grid = N个pos
        /// </summary>
        public int PosOfGrid { get; set; } = 10;

        /// <summary>
        /// 比例
        /// </summary>
        public double Mmpp { get; set; } = 0.1;




        #region

        /// <summary>
        /// 使能修正
        /// </summary>
        public bool Enable { set; get; }

        /// <summary>
        /// 当前修正的组
        /// </summary>
        public int CurrGroupIndex { get; set; }

        /// <summary>
        /// 当前进度 0 - 100 
        /// </summary>
        public int Progress { get; set; }

        /// <summary>
        /// 数据更新时间
        /// </summary>
        public DateTime[] UpdateTimes { get; set; }

        /// <summary>
        /// 正在机架修正中
        /// </summary>
        public bool IsRunning { get; set; }

        /// <summary>
        /// 来回次数
        /// </summary>
        public int ScanCnt { get; set; }


        #endregion

        /// <summary>
        /// 当前被选择的组序号
        /// </summary>
        public int SelectedGroupIndex { get; set; }


        #region 平滑方式生成修正曲线

        /// <summary>
        /// 平均滤波平滑数,单位grid
        /// </summary>
        public int SmoothFactor { get; set; } = 50;
        #endregion

        #region 关键点方式生成修正曲线


        public KeyPointsSelectMode KpSelectMode { get; set; } = KeyPointsSelectMode.Null;

        #endregion

        /// <summary>
        /// 数据好了！！！！
        /// 当 flyad7 的poslen, posOfGrid 发生变化时，DataOK = false, 需要重新记录。
        /// </summary>
        public bool IsDataOK { get; set; } = false;

        public double AvgAd { get; set; }

        #region CMD
        public RelayCommand SelectedGroup0Cmd { get; }
        public RelayCommand SelectedGroup1Cmd { get; }

        public RelayCommand StopCmd { get; }
        public RelayCommand StartCmd { get; }

        public RelayCommand ClearCmd { get; }

        public RelayCommand CreateCorrBySmoothCmd { get; }
        public RelayCommand CreateCorrByKeyPointCmd { get; }
        /// <summary>
        /// 添加关键点模式
        /// </summary>
        public RelayCommand AddKeyPointModeCmd { get; }
        public RelayCommand RemoveKeyPointModeCmd { get; }
        public RelayCommand MoveKeyPointModeCmd { get; }
        public RelayCommand CancelKeyPointModeCmd { get; }
        public RelayCommand SetCorrDataCmd { get; }

        public RelayCommand SaveXlsxCmd { get; }
        public RelayCommand LoadXlsxCmd { get; }

        #endregion

        IScanCorrService scanCorrService;
        IInitParamService initParamService;
        ITDGageService gageService;
        KeyPointLine keyPointLine;

        Dictionary<object, List<Misc.BindingOperations.PropertyChangedEventContexts>> bindingConexts = new Dictionary<object, List<Misc.BindingOperations.PropertyChangedEventContexts>>();

        /// <summary>
        /// 数据已经绑定了
        /// </summary>
        bool isBinding;
        public PgScanCorrVm()
        {
            #region 与数据无关界面参数

            XFormatter = (x) =>
            {
                //x 是 grid
                int pos = (int)(x * PosOfGrid);
                int mm = (int)(pos * Mmpp);
                return $"{pos}\n{mm}mm";
            };
            YFormatter = (y) =>
            {
                return $"{y:F0}";
            };
            Mapper = Mappers.Xy<XY>()
                .X((value, index) => value.X)
                .Y(value => value.Y);



            string seriesName0 = (string)Application.Current.TryFindResource("str.PgScanCorr.ForwOrigData");
            string seriesName1 = (string)Application.Current.TryFindResource("str.PgScanCorr.BackwOrigData");
            string seriesName2 = (string)Application.Current.TryFindResource("str.PgScanCorr.ForwSmoothData");
            string seriesName3 = (string)Application.Current.TryFindResource("str.PgScanCorr.BackwSmoothData");
            string seriesName4 = (string)Application.Current.TryFindResource("str.PgScanCorr.KeyPoint");

            SeriesInfos = new SeriesInfo[] {
                new SeriesInfo(){Name = seriesName0, Color = FLY.ControlLibrary.Themes.Styles.GetForeground(0)},
                new SeriesInfo(){Name = seriesName1, Color = FLY.ControlLibrary.Themes.Styles.GetForeground(1)},
                new SeriesInfo(){Name = seriesName2, Color = FLY.ControlLibrary.Themes.Styles.GetForeground(0,true)},
                new SeriesInfo(){Name = seriesName3, Color = FLY.ControlLibrary.Themes.Styles.GetForeground(1,true)},
            };




            for (int i = 0; i < SeriesInfos.Count(); i++)
            {
                var seriesInfo = SeriesInfos[i];
                var lineSeries = new LineSeries
                {
                    Values = seriesInfo.Datas,
                    StrokeThickness = 2,
                    Title = seriesInfo.Name,
                    Fill = new SolidColorBrush(System.Windows.Media.Colors.Transparent),
                    Stroke = seriesInfo.Color,
                    PointGeometry = null
                };
                Series.Add(lineSeries);
            }

            keyPointLine = new KeyPointLine();
            Misc.BindingOperations.SetBinding(this, nameof(SmoothFactor), keyPointLine, nameof(keyPointLine.SmoothFactor));
            {
                var lineSeries = new LineSeries
                {
                    Title = seriesName4,
                    Values = KeyPointDatas,
                    StrokeThickness = 6,
                    Fill = new SolidColorBrush(System.Windows.Media.Colors.Transparent),
                    Stroke = FLY.ControlLibrary.Themes.Styles.GetForeground(2),
                    Configuration = Mapper,
                    //PointGeometry = Geometry.
                    PointGeometrySize = 15,
                    //PointForeground = "#222E31"
                };
                Series.Add(lineSeries);
            }
            keyPointLine.KeyPoints.CollectionChanged += (s, e) =>
            {
                UpdateKeyPointDatas();
            };
            #endregion

            StopCmd = new RelayCommand(Stop);
            StartCmd = new RelayCommand(Start);
            ClearCmd = new RelayCommand(Clear);
            CreateCorrBySmoothCmd = new RelayCommand(CreateCorrBySmooth);
            CreateCorrByKeyPointCmd = new RelayCommand(CreateCorrByKeyPoint);
            AddKeyPointModeCmd = new RelayCommand(AddKeyPointMode);
            RemoveKeyPointModeCmd = new RelayCommand(RemoveKeyPointMode);
            MoveKeyPointModeCmd = new RelayCommand(MoveKeyPointMode);
            CancelKeyPointModeCmd = new RelayCommand(CancelKeyPointMode);
            SetCorrDataCmd = new RelayCommand(SetCorrData);

            SelectedGroup0Cmd = new RelayCommand(SelectedGroup0);
            SelectedGroup1Cmd = new RelayCommand(SelectedGroup1);

            SaveXlsxCmd = new RelayCommand(SaveXlsx);
            LoadXlsxCmd = new RelayCommand(LoadXlsx);

        }



        public void Init(
            IScanCorrService scanCorrService,
            IInitParamService initParamService,
            ITDGageService gageService
            )
        {
            this.scanCorrService = scanCorrService;
            this.initParamService = initParamService;
            this.gageService = gageService;
            //参数绑定
            SetBinding();

        }

        /// <summary>
        /// 参数绑定
        /// </summary>
        public void SetBinding()
        {
            if (isBinding)//已经绑定了
                return;
            isBinding = true;
            //下面全部event保存在bindingConexts
            Misc.BindingOperations.StartMarkdownEvents(bindingConexts);

            Misc.BindingOperations.SetBinding(this.scanCorrService, nameof(scanCorrService.Enable), this, nameof(Enable), BindingOperations.BindingMode.TwoWay);
            Misc.BindingOperations.SetBinding(this.scanCorrService, nameof(scanCorrService.ScanCnt), this, nameof(ScanCnt));
            Misc.BindingOperations.SetBinding(this.scanCorrService, nameof(scanCorrService.CurrGroupIndex), this, nameof(CurrGroupIndex));
            Misc.BindingOperations.SetBinding(this.scanCorrService, nameof(scanCorrService.Progress), this, nameof(Progress));
            Misc.BindingOperations.SetBinding(this.scanCorrService, nameof(scanCorrService.IsRunning), this, nameof(IsRunning));
            Misc.BindingOperations.SetBinding(this.scanCorrService, nameof(scanCorrService.SmoothFactor), this, nameof(SmoothFactor));

            Misc.BindingOperations.SetBinding(this.initParamService, nameof(initParamService.Encoder1_mmpp), this, nameof(Mmpp));
            Misc.BindingOperations.SetBinding(this.initParamService, nameof(initParamService.PosLength), this, nameof(PosLength));
            Misc.BindingOperations.SetBinding(this.initParamService, nameof(initParamService.PosOfGrid), this, nameof(PosOfGrid));

            //备份event完毕, 必须关闭记录
            Misc.BindingOperations.StopMarkdownEvents();

            UpdateTimes = new DateTime[2];
            downloadAll();

            this.PropertyChanged += PgScanCorrVm_PropertyChanged;

            this.scanCorrService.PropertyChanged += ScanCorrService_PropertyChanged;
        }
        /// <summary>
        /// 解除绑定
        /// </summary>
        public void DisposeBinding()
        {
            if (!isBinding)//已经解除绑定了
                return;
            isBinding = false;
            Misc.BindingOperations.DisposeEventTargetObject(bindingConexts);

            this.PropertyChanged -= PgScanCorrVm_PropertyChanged;

            this.scanCorrService.PropertyChanged -= ScanCorrService_PropertyChanged;
        }
        private void ScanCorrService_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == nameof(FObjBase.FObjServiceClient.IsConnected))
            {
                var client = scanCorrService as FObjBase.FObjServiceClient;
                if (client.IsConnected)
                {
                    downloadAll();
                }
            }
            else if (e.PropertyName == nameof(scanCorrService.UpdateTimes))
            {
                if (UpdateTimes[SelectedGroupIndex] != scanCorrService.UpdateTimes[SelectedGroupIndex])
                {
                    //当前显示的，发生变化了，下载
                    downloadAll();
                }
            }
        }

        private void Clear()
        {
            scanCorrService.Clear(SelectedGroupIndex);
            string tit = (string)Application.Current.TryFindResource("str.PgScanCorr.ExecutedSuccessfully");
            FLY.ControlLibrary.Window_Tip.Show(tit,null,TimeSpan.FromSeconds(2));
        }

        private void Start()
        {
            scanCorrService.Start(SelectedGroupIndex, ScanCnt, SmoothFactor);
            string tit = (string)Application.Current.TryFindResource("str.PgScanCorr.StartOfExecution");
            FLY.ControlLibrary.Window_Tip.Show(tit, null, TimeSpan.FromSeconds(2));
        }

        private void Stop()
        {
            gageService.StartP2(Base.Common.STARTP2_MODE.STOP);
            string tit = (string)Application.Current.TryFindResource("str.PgScanCorr.StopOfExecution");
            FLY.ControlLibrary.Window_Tip.Show(tit, null, TimeSpan.FromSeconds(2));
        }
        private void SelectedGroup1()
        {
            SelectedGroupIndex = 1;
        }

        private void SelectedGroup0()
        {
            SelectedGroupIndex = 0;
        }

        private void SetCorrData()
        {
            //TODO
            //判断是否有数据
            for (int i = 2; i <= 3; i++)
            {

            }

            int[][] corrDatas = new int[2][];
            for (int i = 0; i < 2; i++)
                corrDatas[i] = SeriesInfos[2 + i].Datas.Select(d => double.IsNaN(d) ? Misc.MyBase.NULL_VALUE : (int)d).ToArray();

            int[][] orgDatas = new int[2][];
            for (int i = 0; i < 2; i++)
                orgDatas[i] = SeriesInfos[i].Datas.Select(d => double.IsNaN(d) ? Misc.MyBase.NULL_VALUE : (int)d).ToArray();

            //发送出去
            scanCorrService.SetCorrData(SelectedGroupIndex, corrDatas, (int)AvgAd, orgDatas);

            string tit = (string)Application.Current.TryFindResource("str.PgScanCorr.ApplySuccessfully");
            FLY.ControlLibrary.Window_Tip.Show(tit, null, TimeSpan.FromSeconds(2));

        }

        private void MoveKeyPointMode()
        {
            KpSelectMode = KeyPointsSelectMode.Move;
        }

        private void RemoveKeyPointMode()
        {
            KpSelectMode = KeyPointsSelectMode.Remove;
        }

        private void AddKeyPointMode()
        {
            KpSelectMode = KeyPointsSelectMode.Add;
        }
        private void CancelKeyPointMode()
        {
            KpSelectMode = KeyPointsSelectMode.Null;
        }


        private void CreateCorrByKeyPoint()
        {
            CancelKeyPointMode();

            if (keyPointLine.KeyPoints.Count() < 3)
            {
                //少于3个点
                string tit = (string)Application.Current.TryFindResource("str.PgScanCorr.MakeFailed");
                string msg = (string)Application.Current.TryFindResource("str.PgScanCorr.KeyPointMustMoreThan3");
                FLY.ControlLibrary.Window_WarningTip.Show(tit, msg, TimeSpan.FromSeconds(2));
                return;
            }
            for (int i = 0; i < 2; i++)
            {
                var filterDatas = keyPointLine.CalFilterDatas(SeriesInfos[i].Datas);
                SeriesInfos[i + 2].Datas.Clear();
                SeriesInfos[i + 2].Datas.AddRange(filterDatas);
            }

            //计算平均值
            double avg0 = SeriesInfos[2].Datas.AverageNoNull();
            double avg1 = SeriesInfos[3].Datas.AverageNoNull();
            AvgAd = (int)((avg0 + avg1) / 2);

            {
                string tit = (string)Application.Current.TryFindResource("str.PgScanCorr.MakeSuccessfully");
                FLY.ControlLibrary.Window_Tip.Show(tit,null,TimeSpan.FromSeconds(2));
            }
        }



        void Smooth(ChartValues<double> orgDatas, ChartValues<double> filterDatas)
        {

            var filters = new double[orgDatas.Count()];
            for (int i = 0; i < orgDatas.Count(); i++)
            {
                double sum = 0;
                int cnt = 0;
                for (int j = 0; j < SmoothFactor; j++)
                {
                    int index = i - SmoothFactor / 2 + j;
                    if (index < 0)
                        continue;
                    else if (index >= orgDatas.Count())
                        break;

                    if (!double.IsNaN(orgDatas[index]))
                    {
                        sum += orgDatas[index];
                        cnt++;
                    }
                }
                if (cnt > 0)
                    filters[i] = sum / cnt;
                else
                    filters[i] = double.NaN;
            }
            filterDatas.Clear();
            filterDatas.AddRange(filters);
        }
        private void CreateCorrBySmooth()
        {
            for (int i = 0; i < 2; i++)
                Smooth(SeriesInfos[i].Datas, SeriesInfos[2 + i].Datas);

            //计算平均值
            double avg0 = SeriesInfos[2].Datas.AverageNoNull();
            double avg1 = SeriesInfos[3].Datas.AverageNoNull();
            AvgAd = (int)((avg0 + avg1) / 2);

            
            string tit = (string)Application.Current.TryFindResource("str.PgScanCorr.MakeSuccessfully");
            FLY.ControlLibrary.Window_Tip.Show(tit, null, TimeSpan.FromSeconds(2));
            
        }




        void downloadAll()
        {
            var client = scanCorrService as FObjBase.FObjServiceClient;
            if (!client.IsConnected)
                return;

            //下载数据
            scanCorrService.GetScanCorrGroup(SelectedGroupIndex, scanCorrService_GetScanCorrGroup, null);

            keyPointLine.Clear();
        }

        void scanCorrService_GetScanCorrGroup(object asyncContext, object retData)
        {

            var reponse = retData as GetScanCorrGroupResponse;
            int groupIndex = reponse.GroupIndex;
            UpdateTimes[groupIndex] = reponse.UpdateTime;
            AvgAd = Misc.MyBase.ISVALIDATA(reponse.Avg) ? reponse.Avg : double.NaN;
            IsDataOK = reponse.IsDataOK;

            SeriesInfos[0].Datas.Clear();
            SeriesInfos[1].Datas.Clear();
            SeriesInfos[2].Datas.Clear();
            SeriesInfos[3].Datas.Clear();

            if (reponse.OrgDatas == null || reponse.OrgDatas.Count() != 2)
                return;

            if (reponse.OrgDatas[0] == null)
                return;
            SeriesInfos[0].Datas.AddRange(reponse.OrgDatas[0].Select(ad => Misc.MyBase.ISVALIDATA(ad) ? ad : double.NaN));

            var list = SeriesInfos[0].Datas.Where((d) =>
            {
                if (double.IsNaN(d))
                    return false;
                else
                    return true;
            });
            //设置Y轴范围
            int max = (int)list.Max();
            int min = (int)list.Min();
            //扩大3倍显示
            int range = max - min;
            if (range == 0)
            {
                //异常
            }
            else
            {
                ymax = max + range / 4;
                ymin = min - range / 4;
                YRangeSliderMin = min - range;
                YRangeSliderMax = max + range;
            }

            NotifyPropertyChanged(nameof(YMax));
            NotifyPropertyChanged(nameof(YMin));


            if (reponse.OrgDatas[1] == null)
                return;

            SeriesInfos[1].Datas.AddRange(reponse.OrgDatas[1].Select(ad => Misc.MyBase.ISVALIDATA(ad) ? ad : double.NaN));




            if (reponse.CorrDatas == null || reponse.CorrDatas.Count() != 2 || reponse.CorrDatas[0] == null || reponse.CorrDatas[1] == null)
                return;

            SeriesInfos[2].Datas.AddRange(reponse.CorrDatas[0].Select(ad => Misc.MyBase.ISVALIDATA(ad) ? ad : double.NaN));
            SeriesInfos[3].Datas.AddRange(reponse.CorrDatas[1].Select(ad => Misc.MyBase.ISVALIDATA(ad) ? ad : double.NaN));

        }
        private void PgScanCorrVm_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == nameof(SelectedGroupIndex))
            {
                downloadAll();
            }
        }


        public enum SelectType
        {
            Click,
            Move,
            Cancel
        }

        public void Select(SelectType selectType, System.Windows.Point cpBegin, System.Windows.Point cpEnd)
        {
            if (KpSelectMode == KeyPointsSelectMode.Null)
                return;
            switch (KpSelectMode)
            {
                case KeyPointsSelectMode.Add:
                    {
                        if (selectType != SelectType.Click)
                            return;
                        keyPointLine.Add(cpBegin.X, SeriesInfos[0].Datas);
                        return;
                    }
                    break;
                case KeyPointsSelectMode.Remove:
                    {
                        if (selectType != SelectType.Click)
                            return;
                        keyPointLine.Remove(cpBegin.X);
                        return;
                    }
                    break;
            }
        }

        void UpdateKeyPointDatas()
        {
            KeyPointDatas.Clear();
            KeyPointDatas.AddRange(keyPointLine.KeyPoints);
        }


        private async void LoadXlsx()
        {
            //加载数据
            string strDesktopPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);

            OpenFileDialog sfd = new OpenFileDialog();
            sfd.Filter = "xlsx files (*.xlsx)|*.xlsx";
            sfd.InitialDirectory = strDesktopPath;

            if (sfd.ShowDialog() == true)
            {
                string filename = sfd.FileName;
                Exception error = null;
                var ret = await Task.Factory.StartNew(() =>
                {
                    try
                    {
                        LoadXlsx(filename);
                    }
                    catch (Exception e)
                    {
                        error = e;
                        return false;
                    }
                    return true;
                });
                if (ret)
                {
                    //画图
                    string tit = (string)Application.Current.TryFindResource("str.PgScanCorr.LoadSuccessfully");
                    FLY.ControlLibrary.Window_Tip.Show(tit, filename, TimeSpan.FromSeconds(2));
                }
                else
                {
                    string tit = (string)Application.Current.TryFindResource("str.PgScanCorr.LoadFailed");
                    MessageBox.Show($"{error}", tit, MessageBoxButton.OK, MessageBoxImage.Error);
                }

            }
        }
        private void LoadXlsx(string filepath)
        {
            //检测标题

            DataTable dataTable = new DataTable("table_scancorr");
            dataTable.Columns.Add(new DataColumn() { ColumnName = "序号", DataType = typeof(double), Caption = "0" });
            dataTable.Columns.Add(new DataColumn() { ColumnName = "脉冲", DataType = typeof(double), Caption = "0" });
            dataTable.Columns.Add(new DataColumn() { ColumnName = "位置mm", DataType = typeof(double), Caption = "0" });
            for (int j = 0; j < SeriesInfos.Count(); j++)
            {
                dataTable.Columns.Add(new DataColumn()
                {
                    ColumnName = SeriesInfos[j].Name,
                    DataType = typeof(double),
                    Caption = "0"
                });

            }

            using (ExcelPackage p = new ExcelPackage(new FileInfo(filepath)))
            {
                ExcelWorksheet sheet = p.Workbook.Worksheets["机架修正"];
                FromSheet(sheet, dataTable);
            }

            App.Current.Dispatcher.Invoke(() =>
            {
                for (int j = 0; j < 4; j++)
                {
                    List<double> datas = new List<double>();
                    for (int i = 0; i < dataTable.Rows.Count; i++)
                    {
                        var dataRow = dataTable.Rows[i];

                        double ad = System.Convert.ToDouble(dataRow[SeriesInfos[j].Name]);
                        datas.Add(ad);
                    }
                    SeriesInfos[j].Datas.Clear();
                    SeriesInfos[j].Datas.AddRange(datas);
                    datas.AverageNoNull();

                }

                var list = SeriesInfos[0].Datas.Where((d) =>
                {
                    if (double.IsNaN(d))
                        return false;
                    else
                        return true;
                });
                //设置Y轴范围
                int max = (int)list.Max();
                int min = (int)list.Min();
                //扩大3倍显示
                int range = max - min;
                if (range == 0)
                {
                    //异常
                }
                else
                {
                    ymax = max + range / 4;
                    ymin = min - range / 4;
                    YRangeSliderMin = min - range;
                    YRangeSliderMax = max + range;
                }

                NotifyPropertyChanged(nameof(YMax));
                NotifyPropertyChanged(nameof(YMin));

                double avg0 = SeriesInfos[2].Datas.AverageNoNull();
                double avg1 = SeriesInfos[3].Datas.AverageNoNull();
                if (!double.IsNaN(avg0) && !double.IsNaN(avg1))
                {
                    //异常
                    AvgAd = (avg0 + avg1) / 2;
                }
            });

        }

        void FromSheet(ExcelWorksheet sheet, DataTable dataTable)
        {

            int from_row = 1;
            int row = from_row;


            for (int i = 0; i < dataTable.Columns.Count; i++)
            {
                int col = i + 1;
                if ((string)(sheet.Cells[row, col].Value) != dataTable.Columns[i].ColumnName)
                {
                    throw new Exception($"格式出错, 第{i}列 不是{dataTable.Columns[i].ColumnName}");
                }
            }

            row++;
            while (true)
            {
                var newRow = dataTable.NewRow();
                //当一行都是空的，认为没有数据
                int nullCnt = 0;
                for (int j = 0; j < dataTable.Columns.Count; j++)
                {
                    int col = j + 1;
                    object value = sheet.Cells[row, col].Value;

                    if (value is string || value == null)
                    {
                        if (string.IsNullOrEmpty((string)(value)))
                        {
                            //空的
                            nullCnt++;
                            //continue;
                            return;
                        }

                    }
                    try
                    {
                        newRow[j] = sheet.Cells[row, col].Value;
                    }
                    catch (Exception e)
                    {
                        throw e;
                    }
                }
                if (nullCnt == dataTable.Columns.Count)
                {
                    //没有数据了
                    break;
                }
                dataTable.Rows.Add(newRow);
                row++;
            }

        }


        private void SaveXlsx()
        {
            //下载数据
            string strDesktopPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);

            string filename = $"机架修正_组{SelectedGroupIndex}_{DateTime.Now:yyyyMMddHHmmss}.xlsx";
            SaveFileDialog sfd = new SaveFileDialog();
            sfd.Filter = "xlsx files (*.xlsx)|*.xlsx";
            sfd.InitialDirectory = strDesktopPath;
            sfd.FileName = filename;
            if (sfd.ShowDialog() == true)
            {
                filename = sfd.FileName;
                Task.Factory.StartNew(() =>
                {
                    SaveToXlsx(filename);

                    Application.Current.Dispatcher.Invoke(() =>
                    {
                        string tit = (string)Application.Current.TryFindResource("str.PgScanCorr.SaveSuccessfully");
                        FLY.ControlLibrary.Window_Tip.Show(tit, filename, TimeSpan.FromSeconds(2));
                    });
                });
            }
        }

        private void SaveToXlsx(string filepath)
        {
            if (File.Exists(filepath))
            {
                File.Delete(filepath);
            }
            using (ExcelPackage p = new ExcelPackage(new FileInfo(filepath)))
            {
                ExcelWorksheet sheet = p.Workbook.Worksheets.Add("机架修正");
                DataTable dataTable = new DataTable("table_scancorr");
                dataTable.Columns.Add(new DataColumn() { ColumnName = "序号", DataType = typeof(double), Caption = "0" });
                dataTable.Columns.Add(new DataColumn() { ColumnName = "脉冲", DataType = typeof(double), Caption = "0" });
                dataTable.Columns.Add(new DataColumn() { ColumnName = "位置mm", DataType = typeof(double), Caption = "0" });
                for (int j = 0; j < SeriesInfos.Count(); j++)
                {
                    dataTable.Columns.Add(new DataColumn()
                    {
                        ColumnName = SeriesInfos[j].Name,
                        DataType = typeof(double),
                        Caption = "0"
                    });
                }

                int count = SeriesInfos.Max(cv => cv.Datas.Count());
                for (int i = 0; i < count; i++)
                {
                    var dataRow = dataTable.NewRow();
                    dataRow["序号"] = i;
                    dataRow["脉冲"] = i * PosOfGrid;
                    dataRow["位置mm"] = (int)(i * PosOfGrid * Mmpp);
                    for (int j = 0; j < SeriesInfos.Count(); j++)
                    {
                        if (i < SeriesInfos[j].Datas.Count())
                        {
                            //if(!double.IsNaN(SeriesInfos[j].Datas[i]))
                            dataRow[SeriesInfos[j].Name] = SeriesInfos[j].Datas[i];
                        }
                    }
                    dataTable.Rows.Add(dataRow);
                }


                ToSheet(sheet, dataTable);

                p.Save();
            }
            void ToSheet(ExcelWorksheet sheet, DataTable dataTable)
            {
                int from_row = 1;
                int row = from_row;
                //添加标题
                for (int i = 0; i < dataTable.Columns.Count; i++)
                {
                    int col = i + 1;
                    sheet.Cells[row, col].Value = dataTable.Columns[i].ColumnName;
                    //格式
                    sheet.Column(col).Style.Numberformat.Format = dataTable.Columns[i].Caption;
                }

                row++;
                for (int i = 0; i < dataTable.Rows.Count; i++)
                {
                    for (int j = 0; j < dataTable.Columns.Count; j++)
                    {
                        int col = j + 1;
                        sheet.Cells[row, col].Value = dataTable.Rows[i][j];
                    }
                    row++;
                }

                int colcnt = dataTable.Columns.Count;
                int rowcnt = dataTable.Rows.Count;
                var range = sheet.Cells[from_row, 1, from_row + rowcnt, colcnt];

                var tbl = sheet.Tables.Add(range, dataTable.TableName);
                sheet.Cells[sheet.Dimension.Address].AutoFitColumns();
            }

        }
        #region INotifyPropertyChanged 成员

        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
        #endregion

    }

    /// <summary>
    /// 生成关键点曲线
    /// </summary>
    public class KeyPointLine : INotifyPropertyChanged
    {
        public ObservableCollection<XY> KeyPoints { get; } = new ObservableCollection<XY>();
        public int SmoothFactor { get; set; } = 20;



        public event PropertyChangedEventHandler PropertyChanged;



        public void Add(double x, IEnumerable<double> orgDatas)
        {
            if (x < 0 || x >= orgDatas.Count())
                return;//超出范围

            int index = FindIndex(x);
            if (index != -1)
                return;


            double y = CalY(x, orgDatas);

            //没有，可以添加
            Insert(x, y);

        }
        public void Remove(double x)
        {
            int index = FindIndex(x);
            if (index == -1)
                return;

            KeyPoints.RemoveAt(index);
        }

        public void Clear()
        {

            KeyPoints.Clear();
        }

        /// <summary>
        /// 计算 位置为x 在  orgDatas中的y
        /// </summary>
        /// <param name="x"></param>
        /// <param name="orgDatas"></param>
        /// <returns></returns>
        double CalY(double x, IEnumerable<double> orgDatas)
        {

            double sum = 0;
            int cnt = 0;
            for (int j = 0; j < SmoothFactor; j++)
            {
                int index = (int)(x - SmoothFactor / 2 + j);
                if (index < 0)
                    continue;
                else if (index >= orgDatas.Count())
                    break;

                var value = orgDatas.ElementAt(index);
                if (!double.IsNaN(value))
                {
                    sum += value;
                    cnt++;
                }
            }
            if (cnt > 0)
                return sum / cnt;
            else
                return double.NaN;
        }

        int FindIndex(double x)
        {
            for (int i = 0; i < KeyPoints.Count(); i++)
            {

                if (Math.Abs(KeyPoints[i].X - x) < SmoothFactor / 2)
                {
                    return i;
                }
            }
            return -1;
        }
        int Insert(double x, double y)
        {
            for (int i = 0; i < KeyPoints.Count(); i++)
            {
                if (x < KeyPoints[i].X)
                {
                    //就在它前面插入
                    KeyPoints.Insert(i, new XY() { X = x, Y = y });
                    return i;
                }
            }
            //在最后添加
            KeyPoints.Add(new XY() { X = x, Y = y });

            return KeyPoints.Count() - 1;
        }

        List<XY> GetKeyValues(IEnumerable<double> orgDatas)
        {
            List<XY> keyValues = new List<XY>();
            for (int i = 0; i < KeyPoints.Count(); i++)
            {
                int x = (int)KeyPoints[i].X;

                double sum = 0;
                int cnt = 0;
                for (int j = 0; j < SmoothFactor; j++)
                {
                    int index = x - SmoothFactor / 2 + j;

                    if (index < 0)
                        continue;
                    else if (index >= orgDatas.Count())
                        break;
                    var value = orgDatas.ElementAt(index);
                    if (!double.IsNaN(value))
                    {
                        sum += value;
                        cnt++;
                    }
                }
                double y;
                if (cnt > 0)
                    y = sum / cnt;
                else
                    y = double.NaN;
                keyValues.Add(new XY() { X = x, Y = y });
            }
            return keyValues;
        }

        DenseVector Solve(List<XY> keyValues, int i)
        {
            DenseMatrix X = new DenseMatrix(3, 3);
            DenseVector Y = new DenseVector(3);
            var kv0 = keyValues[i - 1];
            var kv1 = keyValues[i];
            var kv2 = keyValues[i + 1];
            X.SetRow(0, new double[] { kv0.X * kv0.X, kv0.X, 1 });
            X.SetRow(1, new double[] { kv1.X * kv1.X, kv1.X, 1 });
            X.SetRow(2, new double[] { kv2.X * kv2.X, kv2.X, 1 });

            Y.SetValues(new double[] { kv0.Y, kv1.Y, kv2.Y });
            DenseVector abc = (DenseVector)X.Solve(Y);
            return abc;
        }
        public double[] CalFilterDatas(IEnumerable<double> orgDatas)
        {
            List<XY> keyValues = GetKeyValues(orgDatas);
            //计算每个点的2次方程 a,b,c
            List<DenseVector> abcList = new List<DenseVector>();
            for (int i = 0; i < keyValues.Count(); i++)
            {
                if (i == 0)
                {
                    DenseVector abc = Solve(keyValues, 1);
                    abcList.Add(abc);
                }
                else if (i == 1)
                {
                    abcList.Add(abcList.Last());
                }
                else if (i == keyValues.Count() - 1)
                {
                    abcList.Add(abcList.Last());
                }
                else
                {
                    DenseVector abc = Solve(keyValues, i);
                    abcList.Add(abc);
                }
            }


            double[] filterDatas = new double[orgDatas.Count()];
            int begin = -1;
            for (int i = 0; i < keyValues.Count(); i++)
            {
                int j = (int)keyValues[i].X;
                filterDatas[j] = keyValues[i].Y;
                for (j = j - 1; j > begin; j--)
                {
                    double y = abcList[i][0] * j * j + abcList[i][1] * j + abcList[i][2];
                    if (i - 1 >= 0)
                    {
                        double y_pre = abcList[i - 1][0] * j * j + abcList[i - 1][1] * j + abcList[i - 1][2];
                        //权重
                        double t = (j - keyValues[i - 1].X) / (keyValues[i].X - keyValues[i - 1].X);
                        y = t * y + (1 - t) * y_pre;
                    }
                    filterDatas[j] = y;
                }
                begin = (int)keyValues[i].X;
            }
            //计算最后的
            {
                int i = keyValues.Count() - 1;
                for (int j = begin + 1; j < filterDatas.Count(); j++)
                {
                    double y = abcList[i][0] * j * j + abcList[i][1] * j + abcList[i][2];
                    filterDatas[j] = y;
                }
            }
            return filterDatas;
        }
    }

    public enum KeyPointsSelectMode
    {
        Null,
        Add,
        Remove,
        Move
    }

    public class PgScanCorrVmUt : INotifyPropertyChanged
    {

        #region 轴调节
        /// <summary>
        /// Y轴拖拉条 最大值
        /// </summary>
        public double YRangeSliderMax { get; set; } = 65535;

        /// <summary>
        /// Y轴拖拉条 最小值
        /// </summary>
        public double YRangeSliderMin { get; set; } = 0;
        #endregion


        #region 曲线
        public Func<double, string> XFormatter { get; private set; }
        public Func<double, string> YFormatter { get; private set; }

        public double XMin => 0;
        public double XMax => PosLength / PosOfGrid;


        /// <summary>
        /// Y轴 最大值
        /// </summary>
        public double YMax { get; set; } = 65535;

        /// <summary>
        /// Y轴 最小值
        /// </summary>
        public double YMin { get; set; }


        public SeriesCollection Series { get; } = new SeriesCollection();

        public class SeriesInfo : INotifyPropertyChanged
        {
            public string Name { get; set; }
            public Brush Color { get; set; }

            public ChartValues<double> Datas = new ChartValues<double>();

            public event PropertyChangedEventHandler PropertyChanged;
        }
        public SeriesInfo[] SeriesInfos { get; set; }

        CartesianMapper<XY> Mapper { get; set; }

        #endregion

        /// <summary>
        /// 机架总长，脉冲
        /// </summary>
        public int PosLength { get; set; } = 8000;

        /// <summary>
        /// 1个grid = N个pos
        /// </summary>
        public int PosOfGrid { get; set; } = 10;

        /// <summary>
        /// 比例
        /// </summary>
        public double Mmpp { get; set; } = 0.1;




        #region

        /// <summary>
        /// 使能修正
        /// </summary>
        public bool Enable { set; get; }

        /// <summary>
        /// 当前修正的组
        /// </summary>
        public int CurrGroupIndex { get; set; }

        /// <summary>
        /// 当前进度 0 - 100 
        /// </summary>
        public int Progress { get; set; }

        /// <summary>
        /// 数据更新时间
        /// </summary>
        public DateTime[] UpdateTimes { get; set; }

        /// <summary>
        /// 正在机架修正中
        /// </summary>
        public bool IsRunning { get; set; }

        /// <summary>
        /// 来回次数
        /// </summary>
        public int ScanCnt { get; set; }


        #endregion

        /// <summary>
        /// 当前被选择的组序号
        /// </summary>
        public int SelectedGroupIndex { get; set; }


        #region 平滑方式生成修正曲线

        /// <summary>
        /// 平均滤波平滑数,单位grid
        /// </summary>
        public int SmoothFactor { get; set; } = 20;
        #endregion

        #region 关键点方式生成修正曲线


        public KeyPointsSelectMode KpSelectMode { get; set; } = KeyPointsSelectMode.Null;

        #endregion

        /// <summary>
        /// 数据好了！！！！
        /// 当 flyad7 的poslen, posOfGrid 发生变化时，DataOK = false, 需要重新记录。
        /// </summary>
        public bool IsDataOK { get; set; } = false;

        public int AvgAd { get; set; }

        public RelayCommand SelectedGroup0Cmd { get; }
        public RelayCommand SelectedGroup1Cmd { get; }

        public RelayCommand StopCmd { get; }
        public RelayCommand StartCmd { get; }

        public RelayCommand ClearCmd { get; }

        public RelayCommand CreateCorrBySmoothCmd { get; }
        public RelayCommand CreateCorrByKeyPointCmd { get; }
        /// <summary>
        /// 添加关键点模式
        /// </summary>
        public RelayCommand AddKeyPointModeCmd { get; }
        public RelayCommand RemoveKeyPointModeCmd { get; }
        public RelayCommand MoveKeyPointModeCmd { get; }
        public RelayCommand CancelKeyPointModeCmd { get; }
        public RelayCommand SetCorrDataCmd { get; }


        public PgScanCorrVmUt()
        {
            #region 与数据无关界面参数

            XFormatter = (x) =>
            {
                //x 是 grid
                int pos = (int)(x * PosOfGrid);
                int mm = (int)(pos * Mmpp);
                return $"{pos}\n{mm}mm";
            };
            YFormatter = (y) =>
            {
                return $"{y:F0}";
            };
            //Mapper = Mappers.Xy<XY>()
            //    .X((value, index) => value.X)
            //    .Y(value => value.Y);

            SeriesInfos = new SeriesInfo[] {
                new SeriesInfo(){Name = "正向原始", Color = FLY.ControlLibrary.Themes.Styles.GetForeground(0)},
                new SeriesInfo(){Name = "反向原始", Color = FLY.ControlLibrary.Themes.Styles.GetForeground(1)},
                new SeriesInfo(){Name = "正向滤波", Color = FLY.ControlLibrary.Themes.Styles.GetForeground(0, true)},
                new SeriesInfo(){Name = "反向滤波", Color = FLY.ControlLibrary.Themes.Styles.GetForeground(1, true)},
            };

            for (int i = 0; i < SeriesInfos.Count(); i++)
            {
                var seriesInfo = SeriesInfos[i];
                var lineSeries = new LineSeries
                {
                    Values = seriesInfo.Datas,
                    StrokeThickness = 2,
                    Title = seriesInfo.Name,
                    Fill = new SolidColorBrush(System.Windows.Media.Colors.Transparent),
                    Stroke = seriesInfo.Color,
                    PointGeometry = null,
                    Configuration = Mapper
                };
                Series.Add(lineSeries);
            }

            #endregion

            PosLength = 24000;
            PosOfGrid = 24;
            Mmpp = 0.1135;
            int gridLen = PosLength / PosOfGrid;
            Random random = new Random();
            int[] orgDatas0 = new int[gridLen];
            int[] orgDatas1 = new int[gridLen];
            int avgAd = 50300;
            int tolerance = 500;
            for (int i = 0; i < gridLen; i++)
            {
                int ad = (int)(Math.Sin(1.0 * i / (gridLen / 3) * Math.PI) * tolerance) + avgAd;
                orgDatas0[i] = ad + (random.Next(10) - 5);
                orgDatas1[i] = ad + (random.Next(10) - 5);
            }

            int[] corrDatas0 = Smooth(orgDatas0);
            int[] corrDatas1 = Smooth(orgDatas1);
            int avg = avgAd;

            GetScanCorrGroupResponse response = new GetScanCorrGroupResponse();
            response.Avg = avg;
            response.OrgDatas = new int[][] { orgDatas0, orgDatas1 };
            response.CorrDatas = new int[][] { corrDatas0, corrDatas1 };

            UpdateTimes = new DateTime[2];
            scanCorrService_GetScanCorrGroup(0, response);

        }
        int[] Smooth(int[] orgDatas, int smoothFactor = 50)
        {

            var filters = new int[orgDatas.Count()];
            for (int i = 0; i < orgDatas.Count(); i++)
            {
                int sum = 0;
                int cnt = 0;
                for (int j = 0; j < smoothFactor; j++)
                {
                    int index = i - smoothFactor / 2;
                    if (index < 0)
                        continue;
                    else if (index >= orgDatas.Count())
                        break;

                    if (Misc.MyBase.ISVALIDATA(orgDatas[index]))
                    {
                        sum += orgDatas[index];
                        cnt++;
                    }
                }
                if (cnt > 0)
                    filters[i] = sum / cnt;
                else
                    filters[i] = Misc.MyBase.NULL_VALUE;
            }
            return filters;
        }

        void scanCorrService_GetScanCorrGroup(object asyncContext, object retData)
        {

            var reponse = retData as GetScanCorrGroupResponse;
            int groupIndex = reponse.GroupIndex;
            UpdateTimes[groupIndex] = reponse.UpdateTime;
            AvgAd = reponse.Avg;
            IsDataOK = reponse.IsDataOK;

            SeriesInfos[0].Datas.Clear();
            SeriesInfos[1].Datas.Clear();
            SeriesInfos[2].Datas.Clear();
            SeriesInfos[3].Datas.Clear();

            if (reponse.OrgDatas == null || reponse.OrgDatas.Count() != 2)
                return;

            SeriesInfos[0].Datas.AddRange(reponse.OrgDatas[0].Select(ad => Misc.MyBase.ISVALIDATA(ad) ? ad : double.NaN));
            SeriesInfos[1].Datas.AddRange(reponse.OrgDatas[1].Select(ad => Misc.MyBase.ISVALIDATA(ad) ? ad : double.NaN));

            int max = (int)SeriesInfos[0].Datas.Max();
            int min = (int)SeriesInfos[0].Datas.Min();
            //设置Y轴范围
            YMax = max;
            YMin = min;

            if (reponse.CorrDatas == null || reponse.CorrDatas.Count() != 2)
                return;

            SeriesInfos[2].Datas.AddRange(reponse.CorrDatas[0].Select(ad => Misc.MyBase.ISVALIDATA(ad) ? ad : double.NaN));
            SeriesInfos[3].Datas.AddRange(reponse.CorrDatas[1].Select(ad => Misc.MyBase.ISVALIDATA(ad) ? ad : double.NaN));

        }




        #region INotifyPropertyChanged 成员

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

    }
}
