using Http.Core.Contexts;
using PCUT.Extensions;
using PCUT.ViewModels;
using System;
using System.IO;
using System.IO.Ports;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Linq;
using Windows.ApplicationModel.Core;
using Windows.UI.Popups;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Input;

// The Content Dialog item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238

namespace PCUT.Pages
{
    public sealed partial class CutFilesDialog : ContentDialog
    {
        private SvgData _svgData => SvgData.Instance;
        public ImageViewModel DisplayModel { get; set; }
        private readonly string _portName;
        private readonly PortSettingViewModel _portSettings;
        private readonly DesignCenterViewModel _designCenterViewModel;
        private readonly CutDialogViewModel ViewModels;

        public CutFilesDialog(
            string portName,
            PortSettingViewModel portSetting,
            DesignCenterViewModel designCenterViewModel)
        {
            this.InitializeComponent();
            DisplayModel = new ImageViewModel();
            _portName = portName;
            _portSettings = portSetting;
            _designCenterViewModel = designCenterViewModel;
            StartButton.IsEnabled = !(portName is null);

            ViewModels = DataContext as CutDialogViewModel;
            ViewModels.Width = Math.Ceiling(_designCenterViewModel.UseOrigin ? _designCenterViewModel.Width : _svgData.NestedWidth.Value);
            ViewModels.Height = Math.Ceiling(_designCenterViewModel.UseOrigin ? _designCenterViewModel.Height : _svgData.NestedHeight.Value);
        }

        private Task _printTask;
        private void ContentDialog_Opened(ContentDialog sender, ContentDialogOpenedEventArgs args)
        {
            var data = _designCenterViewModel.UseOrigin ? _svgData.Data : _svgData.NestedData;
            PreviewWidthDisplay.Text = ViewModels.Width.ToString();
            PreviewHeightDisplay.Text = ViewModels.Height.ToString();
            DisplayModel.SetSource(data);
        }

        private void MainScroll_Loading(FrameworkElement sender, object args)
        {
            var borderRatio = MainColumnDefinition.ActualWidth / PreviewRowDefinition.ActualHeight;
            var ratio = ViewModels.Width / ViewModels.Height;
            if (ratio < borderRatio)
                ((ComponentStyle)Resources["previewStyle"]).SetSize(PreviewRowDefinition, ratio);
            else
                ((ComponentStyle)Resources["previewStyle"]).SetSize(MainColumnDefinition, ratio);
        }

        private CancellationTokenSource _cancellationTokenSource;
        private async Task Print(XDocument nested)
        {
            if (nested == null)
            {
                CancelButton.Content = "Done";
                StartButton.IsEnabled = true;
                return;
            }

            _cancellationTokenSource = new CancellationTokenSource();
            ViewModels.PrintCommand = string.Empty;
            _ = ViewModels.CutFileAsync(_designCenterViewModel.FileId);
            using (var serialPort = new SerialPort(_portName, _portSettings.BaudRate)
            {
                DataBits = _portSettings.Databits,
                Parity = _portSettings.ParityList[_portSettings.SelectedParity],
                StopBits = _portSettings.StopbitsList[_portSettings.SelectedStopbits]
            })
            {
                var errorMessage = string.Empty;
                try
                {
                    await Task.Factory.StartNew(() =>
                    {
                        var debugMode = UserContext.Instance.DebugFeaturesEnabled;
                        var data = nested.ToHPGL(
                            (ViewModels.Width, ViewModels.Height),
                            _portSettings.Speed);
                        serialPort.Open();
                        Task waitTask = Task.CompletedTask;
                        foreach (var (path, waitTime) in data)
                        {
                            _cancellationTokenSource.Token.ThrowIfCancellationRequested();
                            if (debugMode)
                            {
                                _ = CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
                                {
                                    ViewModels.PrintCommand += $"Command: {path}{Environment.NewLine}Estimate: {waitTime.TotalSeconds}s{Environment.NewLine}";
                                });
                            }
                            waitTask.Wait();
                            try
                            {
                                serialPort.Write(path);
                            }
                            catch
                            {
                                throw new TimeoutException();
                            }
                            waitTask = Task.Delay(waitTime, _cancellationTokenSource.Token);
                        }
                    }, _cancellationTokenSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
                }
                catch (Exception ex)
                {
                    if (ex is AggregateException)
                        ex = ex.InnerException;

                    switch (ex)
                    {
                        case UnauthorizedAccessException unauthorizedAccessException:
                            errorMessage = "Access Denied. Port is currently in used.";
                            break;
                        case ArgumentOutOfRangeException argumentOutOfRangeException:
                            errorMessage = "Wrong port settings. Please verify settings value is valid.";
                            break;
                        case IOException iOException:
                            errorMessage = "Port is in invalid state or port settings is not matched. Please check port connection and settings";
                            break;
                        case InvalidOperationException invalidOperationException:
                            errorMessage = "Port is currently in used.";
                            break;
                        case TimeoutException timeoutException:
                            errorMessage = "Failed to send data to port.";
                            break;
                    }
                }
                finally
                {
                    try
                    {
                        serialPort.Close();
                        Task.Delay(1000).Wait();
                    }
                    catch
                    {
                    }
                }

                if (!string.IsNullOrEmpty(errorMessage))
                {
                    DispatchAsync(async () =>
                    {
                        var dialog = new MessageDialog(errorMessage);
                        await dialog.ShowAsync();
                    });
                }
            }
            _printTask = null;
            StartButton.IsEnabled = true;
            CancelButton.Content = "Done";
        }

        private async void DispatchAsync(Action action)
        {
            await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => action.Invoke());
        }

        private void MainScroll_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
        {
            var (offsetX, offsetY) = e.Delta.CalculateTranslation(e.Velocities);
            var scrollViewer = sender as ScrollViewer;
            _ = scrollViewer.ChangeView(scrollViewer.HorizontalOffset - offsetX, scrollViewer.VerticalOffset - offsetY, scrollViewer.ZoomFactor);
        }

        private async void CancelButton_Click(object sender, RoutedEventArgs e)
        {
            CancelButton.IsEnabled = false;
            StartButton.IsEnabled = false;
            CloseButton.IsEnabled = false;
            _cancellationTokenSource?.Cancel();
            if (_printTask != null)
            {
                await _printTask;
                CancelButton.IsEnabled = true;
                StartButton.IsEnabled = true;
                CloseButton.IsEnabled = true;
                if (sender == CloseButton)
                    Hide();
            }
            else
            {
                Hide();
            }    
        }

        private void StartButton_Click(object sender, RoutedEventArgs e)
        {
            StartButton.IsEnabled = false;
            CancelButton.Content = "Cancel";
            var data = _designCenterViewModel.UseOrigin ? _svgData.Data : _svgData.NestedData;
            _printTask = Print(data);
        }
    }
}
