using OxyPlot;
using OxyPlot.Axes;
using OxyPlot.Series;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace TxtProcessorApp
{
    /// <summary>
    /// Класс, представляющий GraphWindow с поддержкой Undo и Redo для удаления точек.
    /// Изменения вносятся только в копии данных и сохраняются по подтверждению пользователя.
    /// </summary>
    public partial class GraphWindow : Window
    {
        public PlotModel TxtBase1Model { get; private set; }
        public PlotModel TxtNoiseModel { get; private set; }
        public PlotModel MagneteAltitudeModel { get; private set; }
        public PlotModel MagneteSharkModel { get; private set; }

        // Сопоставление ScatterSeries с соответствующими DataRow
        private Dictionary<ScatterSeries, List<DataRow>> seriesDataRows = new Dictionary<ScatterSeries, List<DataRow>>();

        // Стэки для Undo и Redo
        private readonly Stack<GraphAction> undoStack = new Stack<GraphAction>();
        private readonly Stack<GraphAction> redoStack = new Stack<GraphAction>();

        // Ссылки на выбранную серию и точку
        private ScatterSeries selectedSeries = null;
        private ScatterPoint selectedPoint = null;

        // Копии DataTable для работы в GraphWindow
        private DataTable txtDataCopy;
        private DataTable magneteDataCopy;

        /// <summary>
        /// Публичные свойства для доступа к копиям DataTable
        /// </summary>
        public DataTable TxtDataCopy => txtDataCopy;
        public DataTable MagneteDataCopy => magneteDataCopy;

        /// <summary>
        /// Конструктор класса.
        /// </summary>
        /// <param name="txtData">DataTable с данными .txt файлов.</param>
        /// <param name="magneteData">DataTable с данными .magnete файлов.</param>
        public GraphWindow(DataTable txtData, DataTable magneteData)
        {
            InitializeComponent();

            // Создаем копии переданных DataTable
            txtDataCopy = txtData.Copy();
            magneteDataCopy = magneteData.Copy();

            InitializePlotModels(txtDataCopy, magneteDataCopy);
            this.DataContext = this;

            // Подписка на событие нажатия мыши на PlotView
            MainPlotView.MouseDown += PlotView_MouseDown;

            UpdateUndoRedoButtons();
        }

        /// <summary>
        /// Метод для обновления состояния кнопок Undo и Redo.
        /// </summary>
        private void UpdateUndoRedoButtons()
        {
            UndoButton.IsEnabled = undoStack.Any();
            RedoButton.IsEnabled = redoStack.Any();
        }

        /// <summary>
        /// Устанавливает равный масштаб для осей X и Y.
        /// </summary>
        private void SetEqualAxisScaling(LinearAxis xAxis, LinearAxis yAxis, IEnumerable<DataRow> dataRows, string xColumn, string yColumn)
        {
            try
            {
                // Вычисление минимальных и максимальных значений
                double minX = dataRows.Min(row => Convert.ToDouble(row[xColumn]));
                double maxX = dataRows.Max(row => Convert.ToDouble(row[xColumn]));
                double minY = dataRows.Min(row => Convert.ToDouble(row[yColumn]));
                double maxY = dataRows.Max(row => Convert.ToDouble(row[yColumn]));

                double rangeX = maxX - minX;
                double rangeY = maxY - minY;

                // Определение максимального диапазона
                double maxRange = Math.Max(rangeX, rangeY);

                // Вычисление центра диапазона
                double centerX = (minX + maxX) / 2;
                double centerY = (minY + maxY) / 2;

                // Установка диапазонов осей для равного масштаба
                xAxis.Minimum = centerX - maxRange / 2;
                xAxis.Maximum = centerX + maxRange / 2;
                yAxis.Minimum = centerY - maxRange / 2;
                yAxis.Maximum = centerY + maxRange / 2;

                // Установка основных шагов
                xAxis.MajorStep = maxRange / 10;
                yAxis.MajorStep = maxRange / 10;
            }
            catch (Exception ex)
            {
                MessageBox.Show($"Ошибка при установке масштаба осей: {ex.Message}", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        /// <summary>
        /// Инициализирует модели графиков.
        /// </summary>
        private void InitializePlotModels(DataTable txtData, DataTable magneteData)
        {
            // График 1: .txt Time UTC vs BASE-1
            TxtBase1Model = new PlotModel { Title = ".txt Time UTC vs BASE-1" };
            TxtBase1Model.Axes.Add(new DateTimeAxis
            {
                Position = AxisPosition.Bottom,
                StringFormat = "HH:mm:ss",
                Title = "Time UTC",
                IntervalType = DateTimeIntervalType.Hours,
                MinorIntervalType = DateTimeIntervalType.Minutes
            });
            TxtBase1Model.Axes.Add(new LinearAxis
            {
                Position = AxisPosition.Left,
                Title = "BASE-1"
            });
            var txtBase1Series = new ScatterSeries
            {
                Title = "BASE-1",
                MarkerType = MarkerType.Circle,
                MarkerSize = 5,
                TrackerFormatString = "Time UTC: {2:HH:mm:ss}\nBASE-1: {4}"
            };
            List<DataRow> txtBase1DataRows = new List<DataRow>();

            foreach (DataRow row in txtData.Rows)
            {
                if (row.RowState == DataRowState.Deleted)
                    continue;

                if (DateTime.TryParse(row["Time UTC"].ToString(), out DateTime timeUtc) &&
                    double.TryParse(row["BASE-1"].ToString(), out double base1))
                {
                    double timeInDouble = DateTimeAxis.ToDouble(timeUtc);
                    txtBase1Series.Points.Add(new ScatterPoint(timeInDouble, base1));
                    txtBase1DataRows.Add(row);
                }
            }
            TxtBase1Model.Series.Add(txtBase1Series);
            seriesDataRows[txtBase1Series] = txtBase1DataRows;

            // График 2: .txt Time UTC vs Noise
            TxtNoiseModel = new PlotModel { Title = ".txt Time UTC vs Noise" };
            TxtNoiseModel.Axes.Add(new DateTimeAxis
            {
                Position = AxisPosition.Bottom,
                StringFormat = "HH:mm:ss",
                Title = "Time UTC",
                IntervalType = DateTimeIntervalType.Hours,
                MinorIntervalType = DateTimeIntervalType.Minutes
            });
            TxtNoiseModel.Axes.Add(new LinearAxis
            {
                Position = AxisPosition.Left,
                Title = "Noise"
            });
            var txtNoiseSeries = new ScatterSeries
            {
                Title = "Noise",
                MarkerType = MarkerType.Circle,
                MarkerSize = 5,
                TrackerFormatString = "Time UTC: {2:HH:mm:ss}\nNoise: {4}"
            };
            List<DataRow> txtNoiseDataRows = new List<DataRow>();

            foreach (DataRow row in txtData.Rows)
            {
                if (row.RowState == DataRowState.Deleted)
                    continue;

                if (DateTime.TryParse(row["Time UTC"].ToString(), out DateTime timeUtc) &&
                    double.TryParse(row["Noise"].ToString(), out double noise))
                {
                    double timeInDouble = DateTimeAxis.ToDouble(timeUtc);
                    txtNoiseSeries.Points.Add(new ScatterPoint(timeInDouble, noise));
                    txtNoiseDataRows.Add(row);
                }
            }
            TxtNoiseModel.Series.Add(txtNoiseSeries);
            seriesDataRows[txtNoiseSeries] = txtNoiseDataRows;

            // График 3: .magnete Долгота UTM vs Широта UTM (Альтитуда)
            MagneteAltitudeModel = new PlotModel { Title = ".magnete Долгота UTM vs Широта UTM (Альтитуда)" };
            var xAxisAltitude = new LinearAxis
            {
                Position = AxisPosition.Bottom,
                Title = "Долгота UTM"
            };
            var yAxisAltitude = new LinearAxis
            {
                Position = AxisPosition.Left,
                Title = "Широта UTM"
            };
            MagneteAltitudeModel.Axes.Add(xAxisAltitude);
            MagneteAltitudeModel.Axes.Add(yAxisAltitude);

            var colorAxisAltitude = new LinearColorAxis
            {
                Position = AxisPosition.Right,
                Palette = OxyPalettes.Jet(500),
                Title = "Альтитуда"
            };
            MagneteAltitudeModel.Axes.Add(colorAxisAltitude);

            var magneteAltitudeSeries = new ScatterSeries
            {
                Title = "Альтитуда",
                MarkerType = MarkerType.Circle,
                MarkerSize = 5,
                TrackerFormatString = "Долгота UTM: {2}\nШирота UTM: {4}\nАльтитуда: {6}"
            };
            List<DataRow> magneteAltitudeDataRows = new List<DataRow>();

            foreach (DataRow row in magneteData.Rows)
            {
                if (row.RowState == DataRowState.Deleted)
                    continue;

                if (double.TryParse(row["Долгота UTM"].ToString(), out double longitudeUTM) &&
                    double.TryParse(row["Широта UTM"].ToString(), out double latitudeUTM) &&
                    double.TryParse(row["Альтитуда"].ToString(), out double altitude))
                {
                    magneteAltitudeSeries.Points.Add(new ScatterPoint(longitudeUTM, latitudeUTM, 5, altitude));
                    magneteAltitudeDataRows.Add(row);
                }
            }
            MagneteAltitudeModel.Series.Add(magneteAltitudeSeries);
            seriesDataRows[magneteAltitudeSeries] = magneteAltitudeDataRows;

            // Установка равного масштаба для осей
            SetEqualAxisScaling(xAxisAltitude, yAxisAltitude, magneteData.Rows.Cast<DataRow>(), "Долгота UTM", "Широта UTM");

            // График 4: .magnete Долгота UTM vs Широта UTM (Shark)
            MagneteSharkModel = new PlotModel { Title = ".magnete Долгота UTM vs Широта UTM (Shark)" };
            var xAxisShark = new LinearAxis
            {
                Position = AxisPosition.Bottom,
                Title = "Долгота UTM"
            };
            var yAxisShark = new LinearAxis
            {
                Position = AxisPosition.Left,
                Title = "Широта UTM"
            };
            MagneteSharkModel.Axes.Add(xAxisShark);
            MagneteSharkModel.Axes.Add(yAxisShark);

            var colorAxisShark = new LinearColorAxis
            {
                Position = AxisPosition.Right,
                Palette = OxyPalettes.Jet(500),
                Title = "Shark"
            };
            MagneteSharkModel.Axes.Add(colorAxisShark);

            var magneteSharkSeries = new ScatterSeries
            {
                Title = "Shark",
                MarkerType = MarkerType.Circle,
                MarkerSize = 5,
                TrackerFormatString = "Долгота UTM: {2}\nШирота UTM: {4}\nShark: {6}"
            };
            List<DataRow> magneteSharkDataRows = new List<DataRow>();

            foreach (DataRow row in magneteData.Rows)
            {
                if (row.RowState == DataRowState.Deleted)
                    continue;

                if (double.TryParse(row["Долгота UTM"].ToString(), out double longitudeUTM) &&
                    double.TryParse(row["Широта UTM"].ToString(), out double latitudeUTM) &&
                    double.TryParse(row["Shark"].ToString(), out double shark))
                {
                    magneteSharkSeries.Points.Add(new ScatterPoint(longitudeUTM, latitudeUTM, 5, shark));
                    magneteSharkDataRows.Add(row);
                }
            }
            MagneteSharkModel.Series.Add(magneteSharkSeries);
            seriesDataRows[magneteSharkSeries] = magneteSharkDataRows;

            // Установка равного масштаба для осей
            SetEqualAxisScaling(xAxisShark, yAxisShark, magneteData.Rows.Cast<DataRow>(), "Долгота UTM", "Широта UTM");

            // Настройка PlotView по умолчанию
            MainPlotView.Model = TxtBase1Model;
        }

        /// <summary>
        /// Обработчик выбора графика из ComboBox.
        /// </summary>
        private void ChartSelector_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            var selectedItem = (sender as ComboBox)?.SelectedItem as ComboBoxItem;
            if (selectedItem != null)
            {
                switch (selectedItem.Content.ToString())
                {
                    case ".txt Time UTC vs BASE-1":
                        MainPlotView.Model = TxtBase1Model;
                        break;
                    case ".txt Time UTC vs Noise":
                        MainPlotView.Model = TxtNoiseModel;
                        break;
                    case ".magnete Долгота UTM vs Широта UTM (Альтитуда)":
                        MainPlotView.Model = MagneteAltitudeModel;
                        break;
                    case ".magnete Долгота UTM vs Широта UTM (Shark)":
                        MainPlotView.Model = MagneteSharkModel;
                        break;
                    default:
                        break;
                }
            }
        }

        /// <summary>
        /// Обработчик события MouseDown для PlotView.
        /// </summary>
        private void PlotView_MouseDown(object sender, MouseButtonEventArgs e)
        {
            var plotModel = MainPlotView.Model;
            if (plotModel == null)
                return;

            // Получить позицию клика относительно PlotView
            var mousePos = e.GetPosition(MainPlotView);

            // Преобразование координат клика в данные координаты
            double xData, yData;
            try
            {
                xData = plotModel.DefaultXAxis.InverseTransform(mousePos.X);
                yData = plotModel.DefaultYAxis.InverseTransform(mousePos.Y);
            }
            catch
            {
                // Если преобразование не удалось, выйти
                return;
            }

            // Определить ближайшую точку и расстояние до неё (в пикселях)
            double thresholdInPixels = 10; // Пороговое расстояние в пикселях
            double minDistance = double.MaxValue;
            ScatterSeries nearestSeries = null;
            ScatterPoint nearestPoint = null;

            foreach (var series in plotModel.Series)
            {
                if (series is ScatterSeries scatterSeries)
                {
                    foreach (var point in scatterSeries.Points)
                    {
                        // Перевести данные координаты в экранные
                        double screenX = plotModel.DefaultXAxis.Transform(point.X);
                        double screenY = plotModel.DefaultYAxis.Transform(point.Y);

                        // Вычисление расстояния между кликом и точкой
                        double distance = Math.Sqrt(Math.Pow(mousePos.X - screenX, 2) + Math.Pow(mousePos.Y - screenY, 2));

                        if (distance < minDistance && distance <= thresholdInPixels)
                        {
                            minDistance = distance;
                            nearestSeries = scatterSeries;
                            nearestPoint = point;
                        }
                    }
                }
            }

            if (nearestSeries != null && nearestPoint != null)
            {
                // Сохранить выбранную серию и точку
                selectedSeries = nearestSeries;
                selectedPoint = nearestPoint;

                // Показать контекстное меню в позиции клика
                // Convert mouse position relative to window
                var windowPos = this.PointToScreen(mousePos);

                ShowContextMenu(new System.Windows.Point(windowPos.X, windowPos.Y));

                // Обработка события завершена
                e.Handled = true;
            }
        }

        /// <summary>
        /// Отображает контекстное меню с опцией удаления точки.
        /// </summary>
        /// <param name="position">Позиция клика для отображения меню (в координатах экрана).</param>
        private void ShowContextMenu(System.Windows.Point position)
        {
            if (selectedSeries == null || selectedPoint == null)
                return;

            // Создание контекстного меню
            ContextMenu contextMenu = new ContextMenu();

            MenuItem deleteItem = new MenuItem
            {
                Header = "Удалить точку"
            };
            deleteItem.Click += DeleteItem_Click;

            contextMenu.Items.Add(deleteItem);

            // Установка позиции контекстного меню
            contextMenu.Placement = System.Windows.Controls.Primitives.PlacementMode.AbsolutePoint;
            contextMenu.HorizontalOffset = position.X;
            contextMenu.VerticalOffset = position.Y;

            // Открытие контекстного меню
            contextMenu.IsOpen = true;
        }

        /// <summary>
        /// Обработчик события удаления точки из контекстного меню.
        /// </summary>
        private void DeleteItem_Click(object sender, RoutedEventArgs e)
        {
            if (selectedSeries != null && selectedPoint != null)
            {
                var result = MessageBox.Show("Вы уверены, что хотите удалить эту точку?", "Подтверждение удаления", MessageBoxButton.YesNo, MessageBoxImage.Question);
                if (result == MessageBoxResult.Yes)
                {
                    // Найти соответствующую DataRow
                    var dataRows = seriesDataRows[selectedSeries];
                    int pointIndex = selectedSeries.Points.IndexOf(selectedPoint);
                    if (pointIndex >= 0 && pointIndex < dataRows.Count)
                    {
                        DataRow rowToDelete = dataRows[pointIndex];
                        // Помечаем строку как удаленную
                        rowToDelete.Delete();

                        // Удаление точки из ScatterSeries и списка DataRows
                        selectedSeries.Points.RemoveAt(pointIndex);
                        dataRows.RemoveAt(pointIndex);

                        // Запись действия в Undo Stack
                        var action = new GraphAction
                        {
                            Series = selectedSeries,
                            Point = selectedPoint,
                            PointIndex = pointIndex,
                            DataRow = rowToDelete
                        };
                        undoStack.Push(action);

                        // Очистка Redo Stack, так как новое действие делает историю Redo неактуальной
                        redoStack.Clear();

                        // Обновление графика
                        MainPlotView.Model.InvalidatePlot(true);
                    }
                }

                // Сброс выбранной серии и точки
                selectedSeries = null;
                selectedPoint = null;

                // Обновление состояния кнопок
                UpdateUndoRedoButtons();
            }
        }

        /// <summary>
        /// Обработчик кнопки Undo.
        /// </summary>
        private void UndoButton_Click(object sender, RoutedEventArgs e)
        {
            if (!undoStack.Any())
                return;

            var action = undoStack.Pop();
            redoStack.Push(action); // Переносим действие в Redo Stack

            // Восстановление точки
            action.Series.Points.Insert(action.PointIndex, action.Point);
            seriesDataRows[action.Series].Insert(action.PointIndex, action.DataRow);

            // Восстановление строки в DataTable
            action.DataRow.RejectChanges(); // Восстанавливаем удалённую строку

            // Обновление графика
            MainPlotView.Model.InvalidatePlot(true);

            // Обновление состояния кнопок
            UpdateUndoRedoButtons();
        }

        /// <summary>
        /// Обработчик кнопки Redo.
        /// </summary>
        private void RedoButton_Click(object sender, RoutedEventArgs e)
        {
            if (!redoStack.Any())
                return;

            var action = redoStack.Pop();
            undoStack.Push(action); // Переносим действие обратно в Undo Stack

            // Удаление точки снова
            action.Series.Points.RemoveAt(action.PointIndex);
            seriesDataRows[action.Series].RemoveAt(action.PointIndex);

            // Удаление строки из DataTable
            action.DataRow.Delete();

            // Обновление графика
            MainPlotView.Model.InvalidatePlot(true);

            // Обновление состояния кнопок
            UpdateUndoRedoButtons();
        }

        /// <summary>
        /// Обработчик кнопки "Сохранить".
        /// Передает измененные данные обратно в MainWindow.
        /// </summary>
        private void SaveButton_Click(object sender, RoutedEventArgs e)
        {
            var result = MessageBox.Show("Вы уверены, что хотите сохранить изменения?", "Подтверждение сохранения", MessageBoxButton.YesNo, MessageBoxImage.Question);
            if (result == MessageBoxResult.Yes)
            {
                try
                {
                    // Применяем все изменения в копиях DataTable
                    txtDataCopy.AcceptChanges();
                    magneteDataCopy.AcceptChanges();

                    // Закрываем окно с DialogResult = true
                    this.DialogResult = true;
                }
                catch (Exception ex)
                {
                    MessageBox.Show($"Ошибка при сохранении изменений:\n{ex.Message}", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }
        }
        private void ResetZoomMenuItem_Click(object sender, RoutedEventArgs e)
        {
            if (MainPlotView.Model != null)
            {
                foreach (var axis in MainPlotView.Model.Axes)
                {
                    axis.Reset(); // Сбрасывает масштаб оси к автоматическим настройкам
                }
                MainPlotView.Model.InvalidatePlot(false); // Обновляет отображение графика без перерасчета данных
            }
        }
        /// <summary>
        /// Обработчик кнопки "Отмена".
        /// Закрывает окно без сохранения изменений.
        /// </summary>
        private void CancelButton_Click(object sender, RoutedEventArgs e)
        {
            var result = MessageBox.Show("Вы уверены, что хотите отменить все изменения?", "Подтверждение отмены", MessageBoxButton.YesNo, MessageBoxImage.Question);
            if (result == MessageBoxResult.Yes)
            {
                // Откат всех изменений в копиях DataTable
                txtDataCopy.RejectChanges();
                magneteDataCopy.RejectChanges();

                // Закрываем окно с DialogResult = false
                this.DialogResult = false;
            }
        }
    }
}

Серьезность	Код	Описание	Проект	Файл	Строка	Состояние подавления
Предупреждение (активно)	CS8625	Литерал, равный NULL, не может быть преобразован в ссылочный тип, не допускающий значение NULL.	magn	C:\Users\bonia\source\repos\magn\GraphWindow.xaml.cs	33	

Серьезность	Код	Описание	Проект	Файл	Строка	Состояние подавления
Предупреждение (активно)	CS8625	Литерал, равный NULL, не может быть преобразован в ссылочный тип, не допускающий значение NULL.	magn	C:\Users\bonia\source\repos\magn\GraphWindow.xaml.cs	34	






Серьезность	Код	Описание	Проект	Файл	Строка	Состояние подавления
Предупреждение (активно)	CS8618	свойство "TxtBase1Model", не допускающий значения NULL, должен содержать значение, отличное от NULL, при выходе из конструктора. Рассмотрите возможность добавления модификатора "required" или объявления значения свойство, допускающего значение NULL.	magn	C:\Users\bonia\source\repos\magn\GraphWindow.xaml.cs	51	

Серьезность	Код	Описание	Проект	Файл	Строка	Состояние подавления
Предупреждение (активно)	CS8618	свойство "TxtNoiseModel", не допускающий значения NULL, должен содержать значение, отличное от NULL, при выходе из конструктора. Рассмотрите возможность добавления модификатора "required" или объявления значения свойство, допускающего значение NULL.	magn	C:\Users\bonia\source\repos\magn\GraphWindow.xaml.cs	51	

Серьезность	Код	Описание	Проект	Файл	Строка	Состояние подавления
Предупреждение (активно)	CS8618	свойство "MagneteAltitudeModel", не допускающий значения NULL, должен содержать значение, отличное от NULL, при выходе из конструктора. Рассмотрите возможность добавления модификатора "required" или объявления значения свойство, допускающего значение NULL.	magn	C:\Users\bonia\source\repos\magn\GraphWindow.xaml.cs	51	

Серьезность	Код	Описание	Проект	Файл	Строка	Состояние подавления
Предупреждение (активно)	CS8618	свойство "MagneteSharkModel", не допускающий значения NULL, должен содержать значение, отличное от NULL, при выходе из конструктора. Рассмотрите возможность добавления модификатора "required" или объявления значения свойство, допускающего значение NULL.	magn	C:\Users\bonia\source\repos\magn\GraphWindow.xaml.cs	51	

Серьезность	Код	Описание	Проект	Файл	Строка	Состояние подавления
Предупреждение (активно)	CS8600	Преобразование литерала, допускающего значение NULL или возможного значения NULL в тип, не допускающий значение NULL.	magn	C:\Users\bonia\source\repos\magn\GraphWindow.xaml.cs	363	

Серьезность	Код	Описание	Проект	Файл	Строка	Состояние подавления
Предупреждение (активно)	CS8600	Преобразование литерала, допускающего значение NULL или возможного значения NULL в тип, не допускающий значение NULL.	magn	C:\Users\bonia\source\repos\magn\GraphWindow.xaml.cs	364	

Серьезность	Код	Описание	Проект	Файл	Строка	Состояние подавления
Предупреждение (активно)	CS8625	Литерал, равный NULL, не может быть преобразован в ссылочный тип, не допускающий значение NULL.	magn	C:\Users\bonia\source\repos\magn\GraphWindow.xaml.cs	477	

Серьезность	Код	Описание	Проект	Файл	Строка	Состояние подавления
Предупреждение (активно)	CS8625	Литерал, равный NULL, не может быть преобразован в ссылочный тип, не допускающий значение NULL.	magn	C:\Users\bonia\source\repos\magn\GraphWindow.xaml.cs	478