using Autofac;
using Ion.Sdk.Ddr;
using Ion.Sdk.DemoApp.Components;
using Ion.Sdk.DemoApp.Framework;
using Ion.Sdk.DemoApp.Views.ViewModels;
using Ion.Sdk.Ici;
using Ion.Sdk.Idi;
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using Command = Ion.Sdk.DemoApp.Framework.Command;

namespace Ion.Sdk.DemoApp.Views
{
    /// <summary>
    /// Interaction logic for PadfxBrowser.xaml
    /// </summary>
    public partial class PadfxBrowser : UserControl, IAppView
    {


        #region -=Instance members=-
        private readonly IExceptionHandler exceptionHandler;
        private readonly ILifetimeScope scope;
        private readonly IMessagingService messagingService;
        private readonly ViewModel viewModel;
        public IAppViewInfo Info => ViewInfo;
        public string File
        {
            get { return (string)GetValue(FileProperty); }
            set { SetValue(FileProperty, value); }
        }

        public Workspace Workspace
        {
            get { return (Workspace)GetValue(WorkspaceProperty); }
            set { SetValue(WorkspaceProperty, value); }
        }

        public string SelectedPort
        {
            get => (string)GetValue(SelectedPortProperty);
            set => SetValue(SelectedPortProperty, value);
        }

        public int SelectedBaudRate
        {
            get => (int)GetValue(SelectedBaudRateProperty);
            set => SetValue(SelectedBaudRateProperty, value);
        }

        public bool? IsOnline
        {
            get => (bool?)GetValue(IsOnlineProperty);
            set => SetValue(IsOnlineProperty, value);
        }

        public string[] AvailablePorts
        {
            get => GetValue(AvailablePortsProperty) as string[];
            set => SetValue(AvailablePortsProperty, value);
        }

        public int[] AvailableBaudRates => new[] { 9600, 57600, 115200 };

        public Command ShowAddMeasurementDialogCommand { get; }
        public Command SaveCommand { get; }
        public Command OpenFileCommand { get; }
        public Command ExportCommand { get; }
        public Command RefreshAvailablePortsCommand { get; }
        public Command DownloadCommand { get; }
        public Command UploadCommand { get; }
        public Command ShowInstrumentInfoCommand { get; }
        public Command ShowUploadLicenseDialogCommand { get; }
        public Command CheckFirmwareUpdateCommand { get; }

        public ProgressIndicator Progress { get => viewModel.Progress; }

        public PadfxBrowser(IExceptionHandler exceptionHandler, IMessagingService messagingService, ViewModel viewModel, ILifetimeScope scope)
        {
            this.scope = scope;
            this.exceptionHandler = exceptionHandler;
            this.messagingService = messagingService;

            ShowAddMeasurementDialogCommand = Command.Create(parameter => ShowDialog(new SelectMeasurement { StructureObject = parameter as StructureObjectViewModel }), parameter=> !((parameter as StructureObjectViewModel)?.Model is UnknownStructureObject));
            ShowUploadLicenseDialogCommand = Command.Create(parameter => ShowDialog<UploadLicenseDialog>(), parameter => IsOnline.HasValue && IsOnline.Value);
            CheckFirmwareUpdateCommand = Command.CreateAsync(CheckFirmwareUpdate, parameter => IsOnline.HasValue && IsOnline.Value);
            RefreshAvailablePortsCommand = Command.Create(parameter => AvailablePorts = System.IO.Ports.SerialPort.GetPortNames());
            DownloadCommand = Command.CreateAsync(Download, parameter => IsOnline.HasValue && IsOnline.Value);
            UploadCommand = Command.CreateAsync(Upload, parameter => IsOnline.HasValue && IsOnline.Value && viewModel?.SelectedWorkspace != null);
            ShowInstrumentInfoCommand = Command.CreateAsync(ShowInstrumentInfo, parameter => IsOnline.HasValue && IsOnline.Value);
            OpenFileCommand = Command.Create(OpenFile);
            SaveCommand = Command.CreateAsync(Save, parameter => !string.IsNullOrEmpty(viewModel?.File) || Workspace != null);

            InitializeComponent();

            Root.DataContext = this.viewModel = viewModel;
            ExportCommand = Command.CreateAsync(Export, CanExport);

            viewModel.PropertyChanged += ViewModel_PropertyChanged;

        }

        #region -=Command actions=-

        private void OpenFile(object parameter)
        {
            if (viewModel == null) return;
            if (parameter is WorkspaceFileLoader fileLoader)
            {
                var dialog = new OpenFileDialog
                {
                    CheckFileExists = true,
                    Filter = fileLoader.OpenDialogFilter,
                    InitialDirectory = System.IO.Path.Combine(Directory.GetCurrentDirectory(), "SampleFiles")
                };

                if (dialog.ShowDialog() == true)
                {
                    File = null;
                    Workspace = null;
                    viewModel.Use(fileLoader);
                    File = dialog.FileName;
                    SaveCommand.RaiseCanExecuteChanged();
                }
            }
        }

        private async Task Save(object parameter)
        {
            Progress.IsBusy = true;
            try
            {
                string file = File;
                if (string.IsNullOrEmpty(file) || System.IO.Path.GetExtension(File).ToLower() != ".padfx")
                {
                    var dialog = new SaveFileDialog
                    {
                        OverwritePrompt = true,
                        Filter = "padfx|*.padfx",
                        FileName = viewModel.SelectedWorkspace.Name,
                        InitialDirectory = System.IO.Path.Combine(Directory.GetCurrentDirectory(), "SampleFiles"),
                        DefaultExt = "padfx|*.padfx"
                    };
                    if (dialog.ShowDialog() == false)
                        return;
                    file = dialog.FileName;
                }
                Progress.IsBusy = true;
                using (var padfx = Channel.Padfx.Open(file))
                {
                    await padfx.UploadWorkspace(viewModel.SelectedWorkspace);
                    viewModel.Use(scope.Resolve<WorkspaceFileLoader.Padfx>());
                }
                File = file;
                SaveCommand.RaiseCanExecuteChanged();
            }
            finally
            {
                Progress.IsBusy = false;
            }
        }

        private bool CanExport(object parameter)
        {
            if (parameter is WorkspaceFileExporter exporter)
            {
                return !exporter.RequiresWorkspace || viewModel?.CanExport == true;
            }

            return false;
        }

        private async Task Export(object parameter)
        {
            if (parameter is WorkspaceFileExporter exporter)
            {
                try
                {

                    string file = string.Empty;
                    SaveFileDialog dialog;
                    if (!exporter.IsMultiFileExporter)
                    {
                        dialog = new SaveFileDialog
                        {
                            OverwritePrompt = true,
                            Filter = exporter.SaveDialogFilter,
                            FileName = viewModel.SelectedWorkspace.Name,
                            InitialDirectory = System.IO.Path.Combine(Directory.GetCurrentDirectory(), "SampleFiles"),
                            DefaultExt = exporter.SaveDialogFilter
                        };

                    }
                    else
                    {
                        dialog = new SaveFileDialog
                        {
                            Filter = exporter.SaveDialogFilter,
                            FileName = "Select Folder",
                            InitialDirectory = System.IO.Path.Combine(Directory.GetCurrentDirectory(), "SampleFiles"),
                            DefaultExt = exporter.SaveDialogFilter
                        };
                    }

                    if (dialog.ShowDialog() == false)
                        return;
                    file = dialog.FileName;
                    Progress.IsBusy = true;

                    await exporter.ExportWorkspace(viewModel.SelectedWorkspace, file);
                }
                finally
                {
                    Progress.IsBusy = false;
                }
            }
        }

        private async Task Download(object parameter)
        {
            int baudRate = SelectedBaudRate;

            CloseDialog();

            if (parameter is string port)
            {
                try
                {
                    Progress.IsBusy = true;


                    List<WorkspaceInfo> workspaces;
                    using (var instrument = await Task.Run(() => Channel.Serial.Open(port, baudRate)))
                    {
                        instrument.Use(Progress);
                        workspaces = new List<WorkspaceInfo>(await instrument.GetWorkspaces());
                    }

                    var browser = new InstrumentBrowser
                    {
                        Workspaces = new List<WorkspaceInfo>(from item in workspaces
                                                             orderby item.Name
                                                             select item)
                    };
                    browser.OnWorkspaceSelected = BrowserWorkspaceSelected;
                    ShowDialog(browser);
                }
                catch (Exception ex)
                {
                    exceptionHandler.Handle(ex);
                }
                finally
                {
                    Progress.IsBusy = false;
                }
            }

            async Task BrowserWorkspaceSelected(WorkspaceInfo workspace)
            {
                CloseDialog();

                Progress.IsBusy = true;
                try
                {
                    using (var instrument = await Task.Run(() => Channel.Serial.Open(port, baudRate)))
                    {
                        instrument.Use(Progress);
                        Workspace = await instrument.DownloadWorkspace(workspace);
                    }

                    File = null;
                }
                catch (Exception ex)
                {
                    exceptionHandler.Handle(ex);
                }
                finally
                {
                    Progress.IsBusy = false;
                }
            }
        }

        private async Task Upload(object parameter)
        {
            var workspace = viewModel?.SelectedWorkspace;
            if (workspace == null) return;

            if (parameter is string port)
            {
                Progress.IsBusy = true;

                try
                {
                    int baudRate = SelectedBaudRate;
                    using (var instrument = await Task.Run(() => Channel.Serial.Open(port, baudRate)))
                    {
                        instrument.Use(Progress);
                        await instrument.UploadWorkspace(workspace);
                    }
                }
                catch (Exception ex)
                {
                    exceptionHandler.Handle(ex);
                }
                finally
                {
                    Progress.IsBusy = false;
                }
            }
        }

        private async Task ShowInstrumentInfo(object parameter)
        {
            CloseDialog();

            if (parameter is string port)
            {
                try
                {
                    Progress.IsBusy = true;

                    int baudRate = SelectedBaudRate;
                    IInstrumentInfo instrumentInfo;
                    using (var instrument = await Task.Run(() => Channel.Serial.Open(port, baudRate)))
                    {
                        instrument.Use(Progress);
                        instrumentInfo = await instrument.GetInstrumentInfo();
                    }

                    var view = new InstrumentInformation
                    {
                        InstrumentProperties = new List<KeyValuePair<string, string>>(instrumentInfo.GetAllProperties())
                    };
                    ShowDialog(view);
                }
                catch (Exception ex)
                {
                    exceptionHandler.Handle(ex);
                }
                finally
                {
                    Progress.IsBusy = false;
                }
            }
        }

        private async Task CheckFirmwareUpdate(object parameter)
        {

            var update = await viewModel.CheckForFirmwareUpdate(SelectedPort, SelectedBaudRate);
            var message = new Message { Subject = "Firmware update" };
            if (update != null)
            {
                message.Text = $"Firmware update (v{update.Version}) is available at \n{update.UpdateUrl}";
            }
            else
            {
                message.Text = "Firmware is up to date.";
            }
            messagingService.Show(message);
        }

        #endregion

        private void ShowDialog<TView>(TView instance = null) where TView : class, IDialog
        {
            CloseDialog();

            var dialog = instance ?? scope.Resolve<TView>();
            dialog.Closed += Dialog_Closed;
            (dialog as IProgressReporter)?.Use(Progress);

            if (dialog is IConnectedDialog connectedDialog)
            {
                connectedDialog.Port = SelectedPort;
                connectedDialog.BaudRate = SelectedBaudRate;
            }

            DialogPresenter.Content = dialog;
        }

        #region -=Event handlers=-

        private void Dialog_Closed(object sender, EventArgs e)
            => CloseDialog();

        private void CloseDialog()
        {
            if (DialogPresenter.Content == null) return;
            if (DialogPresenter.Content is IDialog dialog)
                dialog.Closed -= Dialog_Closed;

            if (DialogPresenter.Content is IDisposable disposable)
                disposable.Dispose();

            DialogPresenter.Content = null;
        }

        private void Popup_OnOpened(object sender, EventArgs e)
        {
            ExportCommand.RaiseCanExecuteChanged();
        }

        private void DataGrid_OnAutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
        {
            if (e.Column is DataGridBoundColumn column)
            {
                // fix binding to handle invalid property name characters such as 'Ω', '.', '[', ']',...
                column.Binding = new Binding("[" + e.PropertyName + "]");
            }
        }
        private void ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == nameof(ViewModel.SelectedWorkspace))
            {
                UploadCommand.RaiseCanExecuteChanged();
            }
        }

        #endregion

        #endregion -=Instance members=-

        #region -=Static members=-

        public static readonly IAppViewInfo ViewInfo = new AppViewInfo { Id = nameof(PadfxBrowser), Name = "Workspace" };

        public static readonly DependencyProperty FileProperty = DependencyProperty.Register(
            "File", typeof(string), typeof(PadfxBrowser), new PropertyMetadata(null, OnFilePropertyChanged));

        public static readonly DependencyProperty WorkspaceProperty = DependencyProperty.Register(
            "Workspace", typeof(Workspace), typeof(PadfxBrowser), new PropertyMetadata(null, OnWorkspacePropertyChanged));

        public static readonly DependencyProperty IsBusyProperty = DependencyProperty.Register("IsBusy", typeof(bool), typeof(PadfxBrowser), new PropertyMetadata(false));

        public static readonly DependencyProperty AvailablePortsProperty = DependencyProperty.Register("AvailablePorts", typeof(string[]), typeof(PadfxBrowser), new PropertyMetadata(System.IO.Ports.SerialPort.GetPortNames()));

        public static readonly DependencyProperty SelectedPortProperty = DependencyProperty.Register(
            "SelectedPort", typeof(string), typeof(PadfxBrowser), new PropertyMetadata(default(string), OnSelectedPortPropertyChanged));

        public static readonly DependencyProperty SelectedBaudRateProperty = DependencyProperty.Register(
            "SelectedBaudRate", typeof(int), typeof(PadfxBrowser), new PropertyMetadata(115200));


        public static readonly DependencyProperty IsOnlineProperty = DependencyProperty.Register("IsOnline", typeof(bool?), typeof(PadfxBrowser),
            new UIPropertyMetadata(default(bool?), (sender, args) =>
            {
                var browser = sender as PadfxBrowser;
                browser?.DownloadCommand.RaiseCanExecuteChanged();
                browser?.UploadCommand.RaiseCanExecuteChanged();
                browser?.ShowInstrumentInfoCommand.RaiseCanExecuteChanged();
                browser?.ShowUploadLicenseDialogCommand.RaiseCanExecuteChanged();
                browser?.CheckFirmwareUpdateCommand.RaiseCanExecuteChanged();
            }));

        private static async void OnSelectedPortPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d is PadfxBrowser view && e.NewValue is string port)
            {
                try
                {
                    view.IsOnline = null;
                    int baudRate = view.SelectedBaudRate;
                    using (var instrument = await Task.Run(() => Channel.Serial.Open(port, baudRate)))
                    {
                        var isOnline = await instrument.IsOnline();
                        if (port == view.SelectedPort)
                            view.IsOnline = isOnline;
                    }
                }
                catch (Exception ex)
                {
                    Trace.TraceError(ex.Message);
                    if (port == view.SelectedPort)
                        view.IsOnline = false;
                }
            }
        }

        private static async void OnFilePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            try
            {
                var view = d as PadfxBrowser;
                if (view?.Root.DataContext is ViewModel viewModel)
                {
                    viewModel.File = (string)e.NewValue;
                    await viewModel.LoadData();
                    view.SaveCommand.RaiseCanExecuteChanged();
                    view.ExportCommand.RaiseCanExecuteChanged();
                }
            }
            catch (Exception ex)
            {
                using (var scope = App.BeginLifetimeScope())
                    scope.Resolve<IExceptionHandler>().Handle(ex);
            }
        }

        private static void OnWorkspacePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            try
            {
                var view = d as PadfxBrowser;
                var viewModel = view?.viewModel;
                if (viewModel != null)
                {
                    viewModel.SelectedWorkspace = e.NewValue as Workspace;
                    view.SaveCommand.RaiseCanExecuteChanged();
                    view.ExportCommand.RaiseCanExecuteChanged();
                    view.UploadCommand.RaiseCanExecuteChanged();
                }
            }
            catch (Exception ex)
            {
                using (var scope = App.BeginLifetimeScope())
                    scope.Resolve<IExceptionHandler>().Handle(ex);
            }
        }

        #endregion -=Static members=-

        #region -=Definitions=-
        public sealed class ViewModel : BindableObject
        {
            #region -=Properties/Fields=-
            private readonly IMessagingService messagingService;
            private string file;
            private Workspace selectedWorkspace = null;
            private WorkspaceFileLoader selectedFileLoader = null;

            public List<WorkspaceFileLoader> FileLoaders { get; }
            public List<WorkspaceFileExporter> FileExporters { get; }

            public ProgressIndicator Progress { get; } = new ProgressIndicator();

            public string File
            {
                get => file;
                set => Set(ref file, value);
            }

            public Workspace SelectedWorkspace
            {
                get => selectedWorkspace;
                set
                {
                    Set(ref selectedWorkspace, value);
                    OnPropertyChanged(nameof(CanExport));
                    OnPropertyChanged(nameof(WorkspaceRegion));
                }
            }

            public Ddr.RegionInfo WorkspaceRegion
            {
                get
                {
                    var workspace = selectedWorkspace;
                    if (workspace == null)
                        return null;
                    return DataDisplayInfos.RegionInfoList.FirstOrDefault(_ => _.MasterRegionInfoId == workspace.Region.RegionId && _.Id == workspace.Region.ChildRegionId);
                }
            }

            public bool CanExport => selectedWorkspace != null;

            #endregion

            public ViewModel(IEnumerable<WorkspaceFileLoader> fileLoaders, IEnumerable<WorkspaceFileExporter> fileExporters, IMessagingService messagingService)
            {
                FileLoaders = fileLoaders.ToList();
                FileExporters = fileExporters.ToList();
                this.messagingService = messagingService;
            }

            #region -=Public methods=-

            public void Use(WorkspaceFileLoader fileLoader)
            {
                selectedFileLoader = fileLoader;
                selectedFileLoader.Use(Progress);
            }

            /// <summary>
            /// Load workspace.
            /// </summary>
            /// <returns></returns>
            public async Task LoadData()
            {
                var loader = selectedFileLoader;
                if (loader != null)
                {

                    Progress.IsBusy = true;

                    try
                    {
                        Workspace workspace = null;
                        if (System.IO.File.Exists(File))
                        {
                            workspace = await loader.Load(File);
                        }
                        SelectedWorkspace = workspace;

                    }
                    finally
                    {
                        Progress.IsBusy = false;
                    }
                }
            }


            public async Task<FirmwareUpdate> CheckForFirmwareUpdate(string port, int baudRate)
            {
                Progress.IsBusy = true;
                try
                {
                    IInstrumentInfo instrumentInfo = null;
                    using (var instrument = Channel.Serial.Open(port, baudRate))
                    {
                        instrumentInfo = await instrument.GetInstrumentInfo();
                    }

                    if (instrumentInfo != null)
                    {
                        return await FirmwareUpdate.Check(instrumentInfo);
                    }
                }
                finally
                {
                    Progress.IsBusy = false;
                }
                return null;
            }
            #endregion
        }

        #endregion -=Definitions=-


    }

    public sealed class GraphTemplateSelector : DataTemplateSelector
    {
        public override DataTemplate SelectTemplate(object item, DependencyObject container)
        {
            if (item is MeasurementGraph graph && container is FrameworkElement element)
            {

                // show as template as templates for other types are not implemented yet.
                return element.TryFindResource("Graph.Table") as DataTemplate;

                //if (graph.Info?.ValueDataType.Id == (int)MeasurementGraph.DataType.Table)
                //    return element.TryFindResource("Graph.Table") as DataTemplate;
                //if (graph.Info?.ValueDataType.Id == (int)MeasurementGraph.DataType.LineGraph)
                //    return element.TryFindResource("Graph.Line") as DataTemplate;
                //if (graph.Info?.ValueDataType.Id == (int)MeasurementGraph.DataType.BarGraph)
                //    return element.TryFindResource("Graph.Bar") as DataTemplate;
                //if (graph.Info?.ValueDataType.Id == (int)MeasurementGraph.DataType.PolarGraph)
                //    return element.TryFindResource("Graph.Polar") as DataTemplate;
                //if (graph.Info?.ValueDataType.Id == (int)MeasurementGraph.DataType.SurfaceGraph)
                //    return element.TryFindResource("Graph.Surface") as DataTemplate;
            }
            return base.SelectTemplate(item, container);
        }
    }

}
