using MainShell.Resources.CustomControl;
using Newtonsoft.Json;
using Stylet;
using System;
using System.Globalization;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;

namespace MainShell.Common
{
    public static class HeaderDeviceStatusOverlayHost
    {
        private static readonly object OverlayRootTag = new object();
        private static readonly HeaderDeviceStatusOverlaySettings OverlaySettings = new HeaderDeviceStatusOverlaySettings();

        public static void Attach(Window window, HeaderDeviceStatusController controller)
        {
            if (window == null)
            {
                throw new ArgumentNullException(nameof(window));
            }

            if (controller == null)
            {
                throw new ArgumentNullException(nameof(controller));
            }

            window.Dispatcher.BeginInvoke(
                DispatcherPriority.Loaded,
                new Action(() => AttachCore(window, controller)));
        }

        private static void AttachCore(Window window, HeaderDeviceStatusController controller)
        {
            Grid existingOverlayRoot = window.Content as Grid;
            if (existingOverlayRoot != null && ReferenceEquals(existingOverlayRoot.Tag, OverlayRootTag))
            {
                return;
            }

            UIElement currentContent = window.Content as UIElement;
            if (currentContent == null)
            {
                return;
            }

            Grid overlayRoot = new Grid();
            overlayRoot.Tag = OverlayRootTag;

            window.Content = null;
            overlayRoot.Children.Add(currentContent);
            overlayRoot.Children.Add(CreateOverlayElement(controller));
            overlayRoot.Children.Add(CreateDebugPanelElement());
            window.Content = overlayRoot;
        }

        private static FrameworkElement CreateOverlayElement(HeaderDeviceStatusController controller)
        {
            HeaderDeviceStatusView statusView = new HeaderDeviceStatusView();
            statusView.DataContext = controller;
            statusView.IsHitTestVisible = false;
            statusView.HorizontalAlignment = HorizontalAlignment.Left;
            statusView.VerticalAlignment = VerticalAlignment.Top;

            Binding marginBinding = new Binding(nameof(HeaderDeviceStatusOverlaySettings.Margin));
            marginBinding.Source = OverlaySettings;
            BindingOperations.SetBinding(statusView, FrameworkElement.MarginProperty, marginBinding);

            Panel.SetZIndex(statusView, short.MaxValue);
            return statusView;
        }

        private static FrameworkElement CreateDebugPanelElement()
        {
            Border panelBorder = new Border();
            panelBorder.Background = new SolidColorBrush(Color.FromArgb(235, 18, 26, 35));
            panelBorder.BorderBrush = new SolidColorBrush(Color.FromRgb(72, 93, 118));
            panelBorder.BorderThickness = new Thickness(1.0);
            panelBorder.CornerRadius = new CornerRadius(6.0);
            panelBorder.Padding = new Thickness(10.0, 8.0, 10.0, 8.0);
            panelBorder.HorizontalAlignment = HorizontalAlignment.Left;
            panelBorder.VerticalAlignment = VerticalAlignment.Top;

            Binding panelMarginBinding = new Binding(nameof(HeaderDeviceStatusOverlaySettings.DebugPanelMargin));
            panelMarginBinding.Source = OverlaySettings;
            BindingOperations.SetBinding(panelBorder, FrameworkElement.MarginProperty, panelMarginBinding);

            Binding panelVisibilityBinding = new Binding(nameof(HeaderDeviceStatusOverlaySettings.IsDebugPanelVisible));
            panelVisibilityBinding.Source = OverlaySettings;
            panelVisibilityBinding.Converter = new BooleanToVisibilityConverter();
            BindingOperations.SetBinding(panelBorder, UIElement.VisibilityProperty, panelVisibilityBinding);

            StackPanel rootPanel = new StackPanel();
            rootPanel.Orientation = Orientation.Vertical;

            DockPanel headerPanel = new DockPanel();

            TextBlock titleText = new TextBlock();
            titleText.Text = "Overlay Debug";
            titleText.Foreground = Brushes.White;
            titleText.FontSize = 13.0;
            titleText.FontWeight = FontWeights.SemiBold;
            titleText.Margin = new Thickness(0.0, 0.0, 8.0, 6.0);
            DockPanel.SetDock(titleText, Dock.Left);
            headerPanel.Children.Add(titleText);

            Button closeButton = CreateDebugButton("隐藏", 44.0);
            closeButton.Margin = new Thickness(6.0, 0.0, 0.0, 6.0);
            closeButton.Click += delegate(object sender, RoutedEventArgs e)
            {
                OverlaySettings.HideDebugPanel();
            };
            DockPanel.SetDock(closeButton, Dock.Right);
            headerPanel.Children.Add(closeButton);

            rootPanel.Children.Add(headerPanel);

            TextBlock summaryText = new TextBlock();
            summaryText.Foreground = new SolidColorBrush(Color.FromRgb(210, 221, 236));
            summaryText.FontSize = 12.0;
            summaryText.Margin = new Thickness(0.0, 0.0, 0.0, 8.0);
            Binding summaryBinding = new Binding(nameof(HeaderDeviceStatusOverlaySettings.PositionSummary));
            summaryBinding.Source = OverlaySettings;
            BindingOperations.SetBinding(summaryText, TextBlock.TextProperty, summaryBinding);
            rootPanel.Children.Add(summaryText);

            rootPanel.Children.Add(CreateDebugInputRow("Left", nameof(HeaderDeviceStatusOverlaySettings.LeftMarginText)));
            rootPanel.Children.Add(CreateDebugInputRow("Top", nameof(HeaderDeviceStatusOverlaySettings.TopMarginText)));
            rootPanel.Children.Add(CreateDebugInputRow("Step", nameof(HeaderDeviceStatusOverlaySettings.StepText)));

            UniformGrid actionGrid = new UniformGrid();
            actionGrid.Columns = 4;
            actionGrid.Rows = 1;
            actionGrid.Margin = new Thickness(0.0, 0.0, 0.0, 8.0);

            Button leftButton = CreateDebugButton("左", 40.0);
            leftButton.Click += delegate(object sender, RoutedEventArgs e)
            {
                OverlaySettings.AdjustMargin(-OverlaySettings.Step, 0.0);
            };
            actionGrid.Children.Add(leftButton);

            Button rightButton = CreateDebugButton("右", 40.0);
            rightButton.Click += delegate(object sender, RoutedEventArgs e)
            {
                OverlaySettings.AdjustMargin(OverlaySettings.Step, 0.0);
            };
            actionGrid.Children.Add(rightButton);

            Button upButton = CreateDebugButton("上", 40.0);
            upButton.Click += delegate(object sender, RoutedEventArgs e)
            {
                OverlaySettings.AdjustMargin(0.0, -OverlaySettings.Step);
            };
            actionGrid.Children.Add(upButton);

            Button downButton = CreateDebugButton("下", 40.0);
            downButton.Click += delegate(object sender, RoutedEventArgs e)
            {
                OverlaySettings.AdjustMargin(0.0, OverlaySettings.Step);
            };
            actionGrid.Children.Add(downButton);

            rootPanel.Children.Add(actionGrid);

            Button applyButton = CreateDebugButton("应用", 0.0);
            applyButton.Width = 178.0;
            applyButton.Margin = new Thickness(0.0, 0.0, 0.0, 0.0);
            applyButton.Click += delegate(object sender, RoutedEventArgs e)
            {
                OverlaySettings.ApplyTextValues();
            };
            rootPanel.Children.Add(applyButton);

            panelBorder.Child = rootPanel;
            Panel.SetZIndex(panelBorder, short.MaxValue);
            return panelBorder;
        }

        private static FrameworkElement CreateDebugInputRow(string labelText, string bindingPath)
        {
            Grid rowGrid = new Grid();
            rowGrid.Margin = new Thickness(0.0, 0.0, 0.0, 6.0);
            rowGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(44.0) });
            rowGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(92.0) });
            rowGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(56.0) });

            TextBlock label = new TextBlock();
            label.Text = labelText;
            label.Foreground = new SolidColorBrush(Color.FromRgb(210, 221, 236));
            label.FontSize = 12.0;
            label.VerticalAlignment = VerticalAlignment.Center;
            Grid.SetColumn(label, 0);
            rowGrid.Children.Add(label);

            TextBox inputBox = CreateDebugTextBox(bindingPath);
            Grid.SetColumn(inputBox, 1);
            rowGrid.Children.Add(inputBox);

            TextBlock hint = new TextBlock();
            hint.Text = "回车";
            hint.Foreground = new SolidColorBrush(Color.FromRgb(150, 167, 188));
            hint.FontSize = 11.0;
            hint.VerticalAlignment = VerticalAlignment.Center;
            hint.HorizontalAlignment = HorizontalAlignment.Right;
            Grid.SetColumn(hint, 2);
            rowGrid.Children.Add(hint);

            return rowGrid;
        }

        private static TextBox CreateDebugTextBox(string bindingPath)
        {
            TextBox inputBox = new TextBox();
            inputBox.Height = 26.0;
            inputBox.Margin = new Thickness(0.0);
            inputBox.Padding = new Thickness(6.0, 2.0, 6.0, 2.0);
            inputBox.Background = new SolidColorBrush(Color.FromRgb(33, 46, 60));
            inputBox.BorderBrush = new SolidColorBrush(Color.FromRgb(96, 122, 149));
            inputBox.Foreground = Brushes.White;
            inputBox.FontSize = 12.0;
            inputBox.VerticalContentAlignment = VerticalAlignment.Center;

            Binding textBinding = new Binding(bindingPath);
            textBinding.Source = OverlaySettings;
            textBinding.Mode = BindingMode.TwoWay;
            textBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
            BindingOperations.SetBinding(inputBox, TextBox.TextProperty, textBinding);

            inputBox.LostFocus += delegate(object sender, RoutedEventArgs e)
            {
                OverlaySettings.ApplyTextValues();
            };
            inputBox.KeyDown += delegate(object sender, KeyEventArgs e)
            {
                if (e.Key != Key.Enter)
                {
                    return;
                }

                OverlaySettings.ApplyTextValues();
                e.Handled = true;
            };

            return inputBox;
        }

        private static Button CreateDebugButton(string content, double width)
        {
            Button button = new Button();
            button.Content = content;
            if (width > 0.0)
            {
                button.Width = width;
            }
            button.Height = 28.0;
            button.Margin = new Thickness(0.0, 0.0, 6.0, 0.0);
            button.Padding = new Thickness(4.0, 0.0, 4.0, 0.0);
            button.Background = new SolidColorBrush(Color.FromRgb(43, 62, 81));
            button.BorderBrush = new SolidColorBrush(Color.FromRgb(96, 122, 149));
            button.Foreground = Brushes.White;
            button.FontSize = 12.0;
            return button;
        }

        private sealed class HeaderDeviceStatusOverlaySettings : PropertyChangedBase, IDisposable
        {
            private const double DefaultTopMargin = 8.0;
            private const double DefaultLeftMargin = 8.0;
            private const double DefaultDebugPanelTopOffset = 50.0;
            private const double DefaultAdjustStep = 1.0;
            private const string ConfigurationDirectoryName = "Configuration";
            private const string ConfigurationFileName = "HeaderDeviceStatusOverlay.json";

            private readonly string _configurationDirectoryPath;
            private readonly string _configurationFilePath;
            private readonly FileSystemWatcher _fileWatcher;
            private double _topMargin = DefaultTopMargin;
            private double _leftMargin = DefaultLeftMargin;
            private bool _isDebugPanelVisible;
            private double _step = DefaultAdjustStep;
            private string _leftMarginText = DefaultLeftMargin.ToString("0.##", CultureInfo.InvariantCulture);
            private string _topMarginText = DefaultTopMargin.ToString("0.##", CultureInfo.InvariantCulture);
            private string _stepText = DefaultAdjustStep.ToString("0.##", CultureInfo.InvariantCulture);

            public HeaderDeviceStatusOverlaySettings()
            {
                _configurationDirectoryPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ConfigurationDirectoryName);
                _configurationFilePath = Path.Combine(_configurationDirectoryPath, ConfigurationFileName);

                EnsureConfigurationFile();
                LoadFromFile();

                _fileWatcher = new FileSystemWatcher(_configurationDirectoryPath, ConfigurationFileName);
                _fileWatcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite | NotifyFilters.Size | NotifyFilters.CreationTime;
                _fileWatcher.Changed += OnConfigurationFileChanged;
                _fileWatcher.Created += OnConfigurationFileChanged;
                _fileWatcher.Renamed += OnConfigurationFileRenamed;
                _fileWatcher.EnableRaisingEvents = true;
            }

            public Thickness Margin
            {
                get
                {
                    return new Thickness(_leftMargin, _topMargin, 0.0, 0.0);
                }
            }

            public Thickness DebugPanelMargin
            {
                get
                {
                    return new Thickness(_leftMargin, _topMargin + DefaultDebugPanelTopOffset, 0.0, 0.0);
                }
            }

            public bool IsDebugPanelVisible
            {
                get
                {
                    return _isDebugPanelVisible;
                }
            }

            public double Step
            {
                get
                {
                    return _step;
                }
            }

            public string LeftMarginText
            {
                get
                {
                    return _leftMarginText;
                }
                set
                {
                    SetAndNotify(ref _leftMarginText, value);
                }
            }

            public string TopMarginText
            {
                get
                {
                    return _topMarginText;
                }
                set
                {
                    SetAndNotify(ref _topMarginText, value);
                }
            }

            public string StepText
            {
                get
                {
                    return _stepText;
                }
                set
                {
                    SetAndNotify(ref _stepText, value);
                }
            }

            public string PositionSummary
            {
                get
                {
                    return string.Format("Left={0:0.##}, Top={1:0.##}, Step={2:0.##}", _leftMargin, _topMargin, _step);
                }
            }

            public void Dispose()
            {
                _fileWatcher.Changed -= OnConfigurationFileChanged;
                _fileWatcher.Created -= OnConfigurationFileChanged;
                _fileWatcher.Renamed -= OnConfigurationFileRenamed;
                _fileWatcher.Dispose();
            }

            public void AdjustMargin(double leftOffset, double topOffset)
            {
                double leftMargin = _leftMargin + leftOffset;
                double topMargin = _topMargin + topOffset;

                UpdateSettings(leftMargin, topMargin, _isDebugPanelVisible, _step);
                PersistCurrentConfiguration();
            }

            public void HideDebugPanel()
            {
                UpdateSettings(_leftMargin, _topMargin, false, _step);
                PersistCurrentConfiguration();
            }

            public void ApplyTextValues()
            {
                double leftMargin;
                double topMargin;
                double step;

                bool canParseLeft = TryParseInput(LeftMarginText, _leftMargin, out leftMargin);
                bool canParseTop = TryParseInput(TopMarginText, _topMargin, out topMargin);
                bool canParseStep = TryParseInput(StepText, _step, out step);

                if (!canParseStep)
                {
                    step = _step;
                }

                step = NormalizeStep(step);

                UpdateSettings(leftMargin, topMargin, _isDebugPanelVisible, step);
                PersistCurrentConfiguration();
            }

            private void OnConfigurationFileChanged(object sender, FileSystemEventArgs e)
            {
                ReloadOnUiThread();
            }

            private void OnConfigurationFileRenamed(object sender, RenamedEventArgs e)
            {
                ReloadOnUiThread();
            }

            private void ReloadOnUiThread()
            {
                Application currentApplication = Application.Current;
                if (currentApplication == null)
                {
                    LoadFromFile();
                    return;
                }

                currentApplication.Dispatcher.BeginInvoke(
                    DispatcherPriority.Background,
                    new Action(LoadFromFile));
            }

            private void EnsureConfigurationFile()
            {
                if (!Directory.Exists(_configurationDirectoryPath))
                {
                    Directory.CreateDirectory(_configurationDirectoryPath);
                }

                if (File.Exists(_configurationFilePath))
                {
                    return;
                }

                HeaderDeviceStatusOverlayPositionConfiguration configuration = new HeaderDeviceStatusOverlayPositionConfiguration();
                configuration.TopMargin = DefaultTopMargin;
                configuration.LeftMargin = DefaultLeftMargin;
                configuration.ShowDebugPanel = false;
                configuration.AdjustStep = DefaultAdjustStep;
                SaveConfiguration(configuration);
            }

            private void LoadFromFile()
            {
                HeaderDeviceStatusOverlayPositionConfiguration configuration = TryReadConfiguration();
                if (configuration == null)
                {
                    return;
                }

                double topMargin = NormalizeMargin(configuration.TopMargin, DefaultTopMargin);
                double leftMargin = NormalizeMargin(configuration.LeftMargin, DefaultLeftMargin);
                double adjustStep = NormalizeStep(configuration.AdjustStep);
                UpdateSettings(leftMargin, topMargin, configuration.ShowDebugPanel, adjustStep);
            }

            private HeaderDeviceStatusOverlayPositionConfiguration TryReadConfiguration()
            {
                for (int attempt = 0; attempt < 3; attempt++)
                {
                    try
                    {
                        string content = File.ReadAllText(_configurationFilePath);
                        if (string.IsNullOrWhiteSpace(content))
                        {
                            return new HeaderDeviceStatusOverlayPositionConfiguration();
                        }

                        HeaderDeviceStatusOverlayPositionConfiguration configuration = JsonConvert.DeserializeObject<HeaderDeviceStatusOverlayPositionConfiguration>(content);
                        return configuration ?? new HeaderDeviceStatusOverlayPositionConfiguration();
                    }
                    catch (IOException)
                    {
                    }
                    catch (UnauthorizedAccessException)
                    {
                    }
                    catch (JsonException)
                    {
                        return null;
                    }
                }

                return null;
            }

            private void SaveConfiguration(HeaderDeviceStatusOverlayPositionConfiguration configuration)
            {
                string content = JsonConvert.SerializeObject(configuration, Formatting.Indented);
                File.WriteAllText(_configurationFilePath, content);
            }

            private void PersistCurrentConfiguration()
            {
                HeaderDeviceStatusOverlayPositionConfiguration configuration = new HeaderDeviceStatusOverlayPositionConfiguration();
                configuration.TopMargin = _topMargin;
                configuration.LeftMargin = _leftMargin;
                configuration.ShowDebugPanel = _isDebugPanelVisible;
                configuration.AdjustStep = _step;
                SaveConfiguration(configuration);
            }

            private static double NormalizeMargin(double value, double fallbackValue)
            {
                if (double.IsNaN(value) || double.IsInfinity(value))
                {
                    return fallbackValue;
                }

                return value;
            }

            private static double NormalizeStep(double value)
            {
                if (double.IsNaN(value) || double.IsInfinity(value) || value <= 0.0)
                {
                    return DefaultAdjustStep;
                }

                return value;
            }

            private void UpdateSettings(double leftMargin, double topMargin, bool isDebugPanelVisible, double step)
            {
                bool hasChanged = !leftMargin.Equals(_leftMargin)
                    || !topMargin.Equals(_topMargin)
                    || !isDebugPanelVisible.Equals(_isDebugPanelVisible)
                    || !step.Equals(_step);
                if (!hasChanged)
                {
                    return;
                }

                _leftMargin = leftMargin;
                _topMargin = topMargin;
                _isDebugPanelVisible = isDebugPanelVisible;
                _step = step;
                LeftMarginText = FormatValue(_leftMargin);
                TopMarginText = FormatValue(_topMargin);
                StepText = FormatValue(_step);
                NotifyOfPropertyChange(nameof(Margin));
                NotifyOfPropertyChange(nameof(DebugPanelMargin));
                NotifyOfPropertyChange(nameof(IsDebugPanelVisible));
                NotifyOfPropertyChange(nameof(Step));
                NotifyOfPropertyChange(nameof(PositionSummary));
            }

            private static bool TryParseInput(string text, double fallbackValue, out double value)
            {
                string trimmedText = string.IsNullOrWhiteSpace(text) ? string.Empty : text.Trim();
                if (string.IsNullOrEmpty(trimmedText))
                {
                    value = fallbackValue;
                    return false;
                }

                return double.TryParse(trimmedText, NumberStyles.Float, CultureInfo.InvariantCulture, out value)
                    || double.TryParse(trimmedText, NumberStyles.Float, CultureInfo.CurrentCulture, out value);
            }

            private static string FormatValue(double value)
            {
                return value.ToString("0.##", CultureInfo.InvariantCulture);
            }
        }

        private sealed class HeaderDeviceStatusOverlayPositionConfiguration
        {
            public double TopMargin { get; set; }

            public double LeftMargin { get; set; }

            public bool ShowDebugPanel { get; set; }

            public double AdjustStep { get; set; }
        }
    }
}