aboutsummaryrefslogtreecommitdiffstats
path: root/Software/.metadata/.plugins/org.eclipse.core.resources/.history/b9/50c20bcfe720001a1b70e45700dd4116
blob: 87c0ad3682f9a5fe80c7e80266525858bfad80c1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using System.Windows.Data;
using System.Windows.Media;
using Tango.Core.Commands;
using Tango.BL.Entities;
using Tango.Integration.Operation;
using Tango.Integration.ExternalBridge;
using Tango.Logging;
using Tango.MachineStudio.Common.Authentication;
using Tango.MachineStudio.Common.Controls;
using Tango.MachineStudio.Common.Diagnostics;
using Tango.MachineStudio.Common.Notifications;
using Tango.MachineStudio.Common.StudioApplication;
using Tango.MachineStudio.Common.Video;
using Tango.MachineStudio.Developer.Navigation;
using Tango.MachineStudio.Developer.Views;
using Tango.Settings;
using Tango.SharedUI;
using Tango.Transport;
using Tango.BL;
using Microsoft.Win32;
using Tango.PMR.Embroidery;
using Tango.EmbroideryUI;
using System.IO;
using System.Windows;
using Tango.Core.Helpers;
using System.Speech.Synthesis;
using System.Media;
using Tango.MachineStudio.Common.EventLogging;
using Tango.MachineStudio.Common.Speech;
using System.Threading;
using Tango.SharedUI.Helpers;
using Tango.Core.DI;
using Tango.MachineStudio.Common;
using Tango.BL.ColorConversion;
using Tango.MachineStudio.Logging.ViewModels;
using Tango.MachineStudio.Logging.Views;

namespace Tango.MachineStudio.Developer.ViewModels
{
    /// <summary>
    /// Represents the developer module main view, view model.
    /// </summary>
    /// <seealso cref="Tango.SharedUI.ViewModel" />
    public class MainViewVM : StudioViewModel<DeveloperModule>
    {
        private static object _syncLock = new object();
        private const string EMB_FORMATS_EXPORT = "Baby Lock (PES)|*.pes|Tajima (DST)|*.dst|EXP|*.exp|PCS|*.pcs|HUS|*.hus|KSM|*.ksm";
        private const string EMB_FORMATS_IMPORT = "Embroidery Files|*.pes;*.hus;*.dst";

        private INotificationProvider _notification;
        private TimeSpan _runningJobEstimatedDuration;
        private JobHandler _jobHandler;
        private DeveloperNavigationManager _navigation;
        private bool _blockInvalidateCommands;
        private IAuthenticationProvider _authentication;
        private ObservablesContext _machineDbContext;
        private ObservablesContext _activeJobDbContext;
        private IEventLogger _eventLogger;
        private ISpeechProvider _speech;
        private DataCapture.ViewModels.MainViewVM _dataCaptureVM;
        private bool _isRecording;
        private DeveloperModuleSettings _settings;
        private Thread _colorConversionThread;
        private bool _hiveOpened;
        private bool _color_changed_from_hive;
        private bool _dialog_shown;

        #region Properties

        private List<ColorConversionSuggestion> _hiveSuggestions;
        /// <summary>
        /// Gets or sets the hive suggestions.
        /// </summary>
        public List<ColorConversionSuggestion> HiveSuggestions
        {
            get { return _hiveSuggestions; }
            set { _hiveSuggestions = value; RaisePropertyChangedAuto(); }
        }

        private ColorConversionSuggestion _selectedSuggestion;
        /// <summary>
        /// Gets or sets the selected suggestion.
        /// </summary>
        public ColorConversionSuggestion SelectedSuggestion
        {
            get { return _selectedSuggestion; }
            set { _selectedSuggestion = value; RaisePropertyChangedAuto(); OnSelectedSuggestionChanged(); }
        }

        private RunningJobStatus _runningJobStatus;
        /// <summary>
        /// Gets or sets the running job status.
        /// </summary>
        public RunningJobStatus RunningJobStatus
        {
            get { return _runningJobStatus; }
            set { _runningJobStatus = value; RaisePropertyChangedAuto(); }
        }

        private ObservableCollection<Machine> _machines;
        /// <summary>
        /// Gets or sets the machines.
        /// </summary>
        public ObservableCollection<Machine> Machines
        {
            get { return _machines; }
            set { _machines = value; RaisePropertyChangedAuto(); }
        }

        private ObservableCollection<ColorSpace> _colorSpaces;
        /// <summary>
        /// Gets or sets the color spaces.
        /// </summary>
        public ObservableCollection<ColorSpace> ColorSpaces
        {
            get { return _colorSpaces; }
            set { _colorSpaces = value; RaisePropertyChangedAuto(); }
        }

        private ObservableCollection<Rml> _rmls;
        /// <summary>
        /// Gets or sets the RMLS.
        /// </summary>
        public ObservableCollection<Rml> Rmls
        {
            get { return _rmls; }
            set { _rmls = value; RaisePropertyChangedAuto(); }
        }

        private ObservableCollection<WindingMethod> _windingMethods;
        /// <summary>
        /// Gets or sets the winding methods.
        /// </summary>
        public ObservableCollection<WindingMethod> WindingMethods
        {
            get { return _windingMethods; }
            set { _windingMethods = value; RaisePropertyChangedAuto(); }
        }

        private ObservableCollection<SpoolType> _spoolTypes;
        /// <summary>
        /// Gets or sets the spool types.
        /// </summary>
        public ObservableCollection<SpoolType> SpoolTypes
        {
            get { return _spoolTypes; }
            set { _spoolTypes = value; RaisePropertyChangedAuto(); }
        }

        /// <summary>
        /// Gets or sets the application manager.
        /// </summary>
        public IStudioApplicationManager ApplicationManager { get; set; }

        /// <summary>
        /// Gets or sets the video capture provider.
        /// </summary>
        public IVideoCaptureProvider VideoCaptureProvider { get; set; }

        protected Machine _selectedMachine;
        /// <summary>
        /// Gets or sets the selected machine.
        /// </summary>
        public Machine SelectedMachine
        {
            get { return _selectedMachine; }
            set
            {
                _selectedMachine = value;
                OnSelectedMachineChanged();
                RaisePropertyChangedAuto();
                InvalidateRelayCommands();

                if (_selectedMachine != null)
                {
                    _selectedMachine.Modified -= SelectedMachine_Modified;
                    _selectedMachine.Modified += SelectedMachine_Modified;
                }
            }
        }

        private List<LiquidTypesRml> _liquidTypesRmls;
        /// <summary>
        /// Gets or sets the liquid types RMLS.
        /// </summary>
        public List<LiquidTypesRml> LiquidTypesRmls
        {
            get { return _liquidTypesRmls; }
            set { _liquidTypesRmls = value; RaisePropertyChangedAuto(); }
        }

        private ProcessParametersTablesGroup _rmlProcessParametersTablesGroup;
        /// <summary>
        /// Gets or sets the RML process parameters table group (cloned).
        /// </summary>
        public ProcessParametersTablesGroup RmlProcessParametersTableGroup
        {
            get { return _rmlProcessParametersTablesGroup; }
            set
            { _rmlProcessParametersTablesGroup = value; RaisePropertyChangedAuto(); OnProcessParametersTableGroupChanged(); }
        }

        private ObservableCollection<ProcessParametersTablesGroup> _groupsHistory;
        /// <summary>
        /// Gets or sets the RML process parameters groups history.
        /// </summary>
        public ObservableCollection<ProcessParametersTablesGroup> GroupsHistory
        {
            get { return _groupsHistory; }
            set { _groupsHistory = value; RaisePropertyChangedAuto(); }
        }

        private ProcessParametersTablesGroup _selectedGroupHistory;
        /// <summary>
        /// Gets or sets the selected process parameters tables group history.
        /// </summary>
        public ProcessParametersTablesGroup SelectedGroupHistory
        {
            get { return _selectedGroupHistory; }
            set { _selectedGroupHistory = value; RaisePropertyChangedAuto(); OnSelectedGroupHistoryChanged(); }
        }

        private ProcessParametersTable _selectedProcessParametersTable;
        /// <summary>
        /// Gets or sets the selected process parameters table.
        /// </summary>
        public ProcessParametersTable SelectedProcessParametersTable
        {
            get { return _selectedProcessParametersTable; }
            set { _selectedProcessParametersTable = value; RaisePropertyChangedAuto(); OnSelectedParametersTableChanged(); }
        }

        private Job _activeJob;
        /// <summary>
        /// Gets or sets the selected machine job.
        /// </summary>
        public Job ActiveJob
        {
            get { return _activeJob; }
            set
            {
                _activeJob = value;
                RaisePropertyChangedAuto();
                OnActiveJobChanged();
            }
        }

        private Job _selectedMachineJob;
        /// <summary>
        /// Gets or sets the selected machine job.
        /// </summary>
        public Job SelectedMachineJob
        {
            get { return _selectedMachineJob; }
            set { _selectedMachineJob = value; RaisePropertyChangedAuto(); }
        }

        private ObservableCollection<Job> _selectedJobs;
        /// <summary>
        /// Gets or sets the selected jobs.
        /// </summary>
        public ObservableCollection<Job> SelectedJobs
        {
            get { return _selectedJobs; }
            set { _selectedJobs = value; RaisePropertyChangedAuto(); }
        }

        private Segment _selectedSegment;
        /// <summary>
        /// Gets or sets the job selected segment.
        /// </summary>
        public Segment SelectedSegment
        {
            get { return _selectedSegment; }
            set { _selectedSegment = value; RaisePropertyChangedAuto(); OnSelectedSegmentChanged(); }
        }

        private ObservableCollection<Segment> _selectedSegments;
        /// <summary>
        /// Gets or sets the selected segments.
        /// </summary>
        public ObservableCollection<Segment> SelectedSegments
        {
            get { return _selectedSegments; }
            set { _selectedSegments = value; RaisePropertyChangedAuto(); }
        }

        private BrushStop _selectedBrushStop;
        /// <summary>
        /// Gets or sets the selected segment selected brush stop.
        /// </summary>
        public BrushStop SelectedBrushStop
        {
            get { return _selectedBrushStop; }
            set { _selectedBrushStop = value; RaisePropertyChangedAuto(); }
        }

        private ObservableCollection<BrushStop> _selectedBrushStops;
        /// <summary>
        /// Gets or sets the selected brush stops.
        /// </summary>
        public ObservableCollection<BrushStop> SelectedBrushStops
        {
            get { return _selectedBrushStops; }
            set { _selectedBrushStops = value; RaisePropertyChangedAuto(); }
        }

        private Rml _selectedRML;
        /// <summary>
        /// Gets or sets the selected RML.
        /// </summary>
        public Rml SelectedRML
        {
            get { return _selectedRML; }
            set
            {
                _selectedRML = value;
                InvalidateLiquidFactorsAndProcessTables();
                RaisePropertyChangedAuto();
                InvalidateRelayCommands();
            }
        }

        private bool _isSideBarOpened;
        /// <summary>
        /// Gets or sets a value indicating whether the configuration panels are opened.
        /// </summary>
        public bool IsSideBarOpened
        {
            get { return _isSideBarOpened; }
            set { _isSideBarOpened = value; RaisePropertyChangedAuto(); }
        }

        private TimeSpan _estimatedDuration;
        /// <summary>
        /// Gets or sets the estimated duration for the selected job.
        /// </summary>
        public TimeSpan EstimatedDuration
        {
            get { return _estimatedDuration; }
            set { _estimatedDuration = value; RaisePropertyChangedAuto(); }
        }

        private bool _isJobRunning;
        /// <summary>
        /// Gets or sets a value indicating whether a job is currently running.
        /// </summary>
        public bool IsJobRunning
        {
            get { return _isJobRunning; }
            set { _isJobRunning = value; RaisePropertyChangedAuto(); }
        }

        private Job _runningJob;
        /// <summary>
        /// Gets or sets the currently running job.
        /// </summary>
        public Job RunningJob
        {
            get { return _runningJob; }
            set { _runningJob = value; RaisePropertyChangedAuto(); }
        }

        private bool _isJobCompleted;
        /// <summary>
        /// Gets or sets a value indicating whether the running job has completed successfully.
        /// </summary>
        public bool IsJobCompleted
        {
            get { return _isJobCompleted; }
            set { _isJobCompleted = value; RaisePropertyChangedAuto(); }
        }

        private bool _isJobFailed;
        /// <summary>
        /// Gets or sets a value indicating whether the running job has failed.
        /// </summary>
        public bool IsJobFailed
        {
            get { return _isJobFailed; }
            set { _isJobFailed = value; RaisePropertyChangedAuto(); }
        }

        private bool _showJobStatus;
        /// <summary>
        /// Gets or sets a value indicating whether to show all the relevant job status areas.
        /// </summary>
        public bool ShowJobStatus
        {
            get { return _showJobStatus; }
            set { _showJobStatus = value; RaisePropertyChangedAuto(); }
        }

        private bool _isJobCanceled;
        /// <summary>
        /// Gets or sets a value indicating whether the last running job was canceled.
        /// </summary>
        public bool IsJobCanceled
        {
            get { return _isJobCanceled; }
            set { _isJobCanceled = value; RaisePropertyChangedAuto(); }
        }

        private IMachineOperator _machineOperator;
        /// <summary>
        /// Gets or sets the machine operator.
        /// </summary>
        public IMachineOperator MachineOperator
        {
            get { return _machineOperator; }
            set { _machineOperator = value; RaisePropertyChangedAuto(); }
        }

        private List<Segment> _runningJobSegments;
        /// <summary>
        /// Gets or sets the running job segments.
        /// </summary>
        public List<Segment> RunningJobSegments
        {
            get { return _runningJobSegments; }
            set { _runningJobSegments = value; RaisePropertyChangedAuto(); }
        }

        private ICollectionView _jobsCollectionView;
        /// <summary>
        /// Gets or sets the jobs collection view.
        /// </summary>
        public ICollectionView JobsCollectionView
        {
            get { return _jobsCollectionView; }
            set
            {
                _jobsCollectionView = value;
                BindingOperations.EnableCollectionSynchronization(_jobsCollectionView, _syncLock);

                RaisePropertyChangedAuto();
            }
        }

        private ICollectionView _segmentsCollectionView;
        /// <summary>
        /// Gets or sets the segments collection view.
        /// </summary>
        public ICollectionView SegmentsCollectionView
        {
            get { return _segmentsCollectionView; }
            set
            {
                _segmentsCollectionView = value;
                BindingOperations.EnableCollectionSynchronization(_segmentsCollectionView, _syncLock);
                RaisePropertyChangedAuto();
            }
        }

        private ICollectionView _brushStopsCollectionView;
        /// <summary>
        /// Gets or sets the brush stops collection view.
        /// </summary>
        public ICollectionView BrushStopsCollectionView
        {
            get { return _brushStopsCollectionView; }
            set
            {
                _brushStopsCollectionView = value;
                BindingOperations.EnableCollectionSynchronization(_brushStopsCollectionView, _syncLock);
                RaisePropertyChangedAuto();
            }
        }

        private String _jobFilter;
        /// <summary>
        /// Gets or sets the job filter.
        /// </summary>
        public String JobFilter
        {
            get { return _jobFilter; }
            set { _jobFilter = value; RaisePropertyChangedAuto(); OnJobFilterChanged(); }
        }

        private ObservableCollection<MachinesEvent> _jobEvents;
        /// <summary>
        /// Gets or sets the running job events.
        /// </summary>
        public ObservableCollection<MachinesEvent> JobEvents
        {
            get { return _jobEvents; }
            set { _jobEvents = value; RaisePropertyChangedAuto(); }
        }

        private MachinesEvent _selectedJobEvent;
        /// <summary>
        /// Gets or sets the selected job event.
        /// </summary>
        public MachinesEvent SelectedJobEvent
        {
            get { return _selectedJobEvent; }
            set { _selectedJobEvent = value; RaisePropertyChangedAuto(); OnSelectedJobEventChanged(); }
        }

        #endregion

        #region Commands

        /// <summary>
        /// Gets or sets the edit machine command.
        /// </summary>
        public RelayCommand EditMachineCommand { get; set; }

        /// <summary>
        /// Gets or sets the edit RML command.
        /// </summary>
        public RelayCommand EditRMLCommand { get; set; }

        /// <summary>
        /// Gets or sets the toggle side bar command.
        /// </summary>
        public RelayCommand ToggleSideBarCommand { get; set; }

        /// <summary>
        /// Gets or sets the save process parameters command.
        /// </summary>
        public RelayCommand SaveProcessParametersCommand { get; set; }

        /// <summary>
        /// Gets or sets the save liquid factors command.
        /// </summary>
        public RelayCommand SaveLiquidFactorsCommand { get; set; }

        /// <summary>
        /// Gets or sets the add segment command.
        /// </summary>
        public RelayCommand AddSegmentCommand { get; set; }

        /// <summary>
        /// Gets or sets the remove segment command.
        /// </summary>
        public RelayCommand RemoveSegmentCommand { get; set; }

        /// <summary>
        /// Gets or sets the add job command.
        /// </summary>
        public RelayCommand AddJobCommand { get; set; }

        /// <summary>
        /// Gets or sets the remove job command.
        /// </summary>
        public RelayCommand RemoveJobCommand { get; set; }

        /// <summary>
        /// Gets or sets the add brush stop command.
        /// </summary>
        public RelayCommand AddBrushStopCommand { get; set; }

        /// <summary>
        /// Gets or sets the remove brush stop command.
        /// </summary>
        public RelayCommand RemoveBrushStopCommand { get; set; }

        /// <summary>
        /// Gets or sets the save job command.
        /// </summary>
        public RelayCommand SaveJobCommand { get; set; }

        /// <summary>
        /// Gets or sets the discard job command.
        /// </summary>
        public RelayCommand DiscardJobCommand { get; set; }

        /// <summary>
        /// Gets or sets the start job command.
        /// </summary>
        public RelayCommand StartJobCommand { get; set; }

        /// <summary>
        /// Gets or sets the start job and record command.
        /// </summary>
        public RelayCommand StartJobAndRecordCommand { get; set; }

        /// <summary>
        /// Gets or sets the stop job command.
        /// </summary>
        public RelayCommand StopJobCommand { get; set; }

        /// <summary>
        /// Gets or sets the close job completion status command.
        /// </summary>
        public RelayCommand CloseJobCompletionStatusCommand { get; set; }

        /// <summary>
        /// Gets or sets the load job command.
        /// </summary>
        public RelayCommand LoadJobCommand { get; set; }

        /// <summary>
        /// Gets or sets the duplicate job command.
        /// </summary>
        public RelayCommand DuplicateJobCommand { get; set; }

        /// <summary>
        /// Gets or sets the duplicate segment command.
        /// </summary>
        public RelayCommand DuplicateSegmentCommand { get; set; }

        /// <summary>
        /// Gets or sets the duplicate brush stop command.
        /// </summary>
        public RelayCommand DuplicateBrushStopCommand { get; set; }

        /// <summary>
        /// Gets or sets the push process parameters command.
        /// </summary>
        public RelayCommand PushProcessParametersCommand { get; set; }

        /// <summary>
        /// Gets or sets the import embroidery file command.
        /// </summary>
        public RelayCommand ImportEmbroideryFileCommand { get; set; }

        /// <summary>
        /// Gets or sets the display job embroidery file command.
        /// </summary>
        public RelayCommand<Job> DisplayJobEmbroideryFileCommand { get; set; }

        /// <summary>
        /// Gets or sets the reload machines command.
        /// </summary>
        public RelayCommand ReloadMachinesCommand { get; set; }
        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="MainViewVM"/> class.
        /// </summary>
        /// <param name="applicationManager">The application manager.</param>
        /// <param name="notificationProvider">The notification provider.</param>
        public MainViewVM(IStudioApplicationManager applicationManager, INotificationProvider notificationProvider, IDiagnosticsFrameProvider diagnosticsFrameProvider, IVideoCaptureProvider videoCaptureProvider, DeveloperNavigationManager navigation, IAuthenticationProvider authentication, IEventLogger eventLogger, ISpeechProvider speech)
        {
            _settings = SettingsManager.Default.GetOrCreate<DeveloperModuleSettings>();

            SelectedJobs = new ObservableCollection<Job>();

            JobEvents = new ObservableCollection<MachinesEvent>();

            LogManager.Log("Initializing machine Db context...");
            _machineDbContext = ObservablesContext.CreateDefault();

            Machines = _machineDbContext.Machines.ToObservableCollection();

            if (_settings.LastSelectedMachineGuid != null)
            {
                LogManager.Log("Setting last selected machine from settings...");
                SelectedMachine = _machineDbContext.Machines.SingleOrDefault(x => x.Guid == _settings.LastSelectedMachineGuid);
            }

            if (_settings.LastSelectedJobGuid != null && SelectedMachine != null)
            {
                LogManager.Log("Setting last selected job from settings...");
                SelectedMachineJob = SelectedMachine.Jobs.SingleOrDefault(x => x.Guid == _settings.LastSelectedJobGuid);
            }

            _authentication = authentication;

            _notification = notificationProvider;
            _speech = speech;
            _navigation = navigation;
            ApplicationManager = applicationManager;
            VideoCaptureProvider = videoCaptureProvider;
            _eventLogger = eventLogger;

            LogManager.Log("Initializing relay commands...");

            TangoIOC.Default.GetInstanceWhenAvailable<DataCapture.ViewModels.MainViewVM>((vm) =>
            {
                _dataCaptureVM = vm;
                _dataCaptureVM.RelayCommandsInvalidated += (_, __) => StartJobAndRecordCommand.RaiseCanExecuteChanged();
            });

            //Initialize Commands...
            EditMachineCommand = new RelayCommand(EditMachine, () => SelectedMachine != null);
            EditRMLCommand = new RelayCommand(EditRML, () => SelectedRML != null);
            ToggleSideBarCommand = new RelayCommand(() => IsSideBarOpened = !IsSideBarOpened);
            SaveLiquidFactorsCommand = new RelayCommand(SaveLiquidFactors, () => SelectedRML != null);
            AddSegmentCommand = new RelayCommand(AddSegment, () => ActiveJob != null);
            RemoveSegmentCommand = new RelayCommand(RemoveSelectedSegments, () => SelectedSegment != null);
            AddJobCommand = new RelayCommand(AddJob, () => SelectedMachine != null);
            RemoveJobCommand = new RelayCommand(RemoveSelectedJobs, () => SelectedMachineJob != null);
            AddBrushStopCommand = new RelayCommand(AddBrushStop, () => SelectedSegment != null);
            RemoveBrushStopCommand = new RelayCommand(RemoveSelectedBrushStops, () => SelectedBrushStop != null);
            SaveJobCommand = new RelayCommand(SaveActiveJob, () => SelectedMachine != null);
            DiscardJobCommand = new RelayCommand(BackToJobs, () => SelectedMachine != null);
            StartJobCommand = new RelayCommand(StartJob, () => ActiveJob != null && !IsJobRunning && MachineOperator != null && !MachineOperator.MachineEventsStateProvider.Events.ToList().Exists(x => x.ActionTypes.Contains(BL.Enumerations.ActionTypes.PreventJobExecution)));
            StartJobAndRecordCommand = new RelayCommand(StartJobAndRecord, () => _dataCaptureVM != null && !_dataCaptureVM.Recorder.IsRecording && !_dataCaptureVM.Player.IsPlaying && ActiveJob != null && !IsJobRunning && MachineOperator != null && !MachineOperator.MachineEventsStateProvider.Events.ToList().Exists(x => x.ActionTypes.Contains(BL.Enumerations.ActionTypes.PreventJobExecution)));
            StopJobCommand = new RelayCommand(StopJob, () => IsJobRunning);
            CloseJobCompletionStatusCommand = new RelayCommand(CloseJobCompletionStatusBar);
            LoadJobCommand = new RelayCommand(LoadSelectedJob, () => SelectedMachineJob != null);
            DuplicateJobCommand = new RelayCommand(DuplicateSelectedJobs, () => SelectedMachineJob != null);
            DuplicateSegmentCommand = new RelayCommand(DuplicateSelectedSegments, () => SelectedSegment != null);
            DuplicateBrushStopCommand = new RelayCommand(DuplicateSelectedBrushStops, () => SelectedBrushStop != null);
            SaveProcessParametersCommand = new RelayCommand(SaveProcessParameters, () => SelectedRML != null && SelectedRML.ProcessParametersTablesGroups.Count > 0);
            PushProcessParametersCommand = new RelayCommand(PushProcessParameters, () => SelectedRML != null && SelectedRML.ProcessParametersTablesGroups.Count > 0 && SelectedProcessParametersTable != null && MachineOperator != null);
            ImportEmbroideryFileCommand = new RelayCommand(ImportEmbroideryFile, () => SelectedMachine != null);
            DisplayJobEmbroideryFileCommand = new RelayCommand<Job>(DisplayJobEmbroideryFile);
            ReloadMachinesCommand = new RelayCommand(ReloadMachine);

            ApplicationManager.ConnectedMachineChanged += ApplicationManager_ConnectedMachineChanged;

            _eventLogger.NewLog += _eventLogger_NewLog;

            _colorConversionThread = new Thread(ColorConversionThreadMethod);
            _colorConversionThread.IsBackground = true;
            _colorConversionThread.Start();
        }

        #endregion

        #region Color Conversion

        private void ColorConversionThreadMethod()
        {
            while (true)
            {
                if (IsVisible && ActiveJob != null && ActiveJob.Segments != null)
                {
                    var stops = ActiveJob.Segments.SelectMany(x => x.BrushStops);

                    foreach (var stop in stops)
                    {
                        if (stop.ColorSpace.Code == BL.Enumerations.ColorSpaces.Volume.ToInt32() && !stop.IsLiquidVolumesOutOfRange)
                        {
                            try
                            {
                                var output = TangoColorConverter.GetSuggestions(stop);
                                stop.Red = output.SingleCoordinates.Red;
                                stop.Green = output.SingleCoordinates.Green;
                                stop.Blue = output.SingleCoordinates.Blue;
                            }
                            catch { }
                        }
                        else if (stop.ColorSpace.Code == BL.Enumerations.ColorSpaces.RGB.ToInt32() && !stop.Corrected)
                        {
                            try
                            {
                                stop.IsOutOfGamut = TangoColorConverter.IsOutOfGamut(stop);
                            }
                            catch { }
                        }
                    }
                }

                Thread.Sleep(500);
            }
        }

        public void OnHivePopupOpened()
        {
            if (SelectedBrushStop != null)
            {
                _hiveOpened = true;
                HiveSuggestions = TangoColorConverter.CreateHiveSuggestions(TangoColorConverter.GetSuggestions(SelectedBrushStop));
            }
        }

        private void OnSelectedSuggestionChanged()
        {
            if (SelectedSuggestion != null && SelectedBrushStop != null && _hiveOpened)
            {
                _color_changed_from_hive = true;
                SelectedBrushStop.Color = SelectedSuggestion.Color;
                SelectedBrushStop.Corrected = true;
                SelectedBrushStop.IsOutOfGamut = false;

                var coords = SelectedSuggestion.Coordinates;

                foreach (var liquid in coords.OutputLiquids)
                {
                    var liquidVolume = SelectedBrushStop.LiquidVolumes.SingleOrDefault(x => x.IdsPack.LiquidType.Code == liquid.LiquidType.ToInt32());

                    if (liquidVolume != null)
                    {
                        liquidVolume.Volume = liquid.Volume;
                    }
                }

                _color_changed_from_hive = false;
            }
        }

        public void OnSelectedBrushColorChanged(Color color)
        {
            if (!_color_changed_from_hive && _hiveOpened)
            {
                SelectedBrushStop.Corrected = false;
                HiveSuggestions = TangoColorConverter.CreateHiveSuggestions(TangoColorConverter.GetSuggestions(SelectedBrushStop));
            }
        }

        public void OnHivePopupClosed()
        {
            _hiveOpened = false;
        }

        #endregion

        #region Event Handlers

        private void _eventLogger_NewLog(object sender, MachinesEvent e)
        {
            if (IsJobRunning)
            {
                InvokeUI(() =>
                {
                    JobEvents.Insert(0, e);
                });
            }
        }

        /// <summary>
        /// Handles the application manager connected machine changes event.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="machine">The machine.</param>
        private void ApplicationManager_ConnectedMachineChanged(object sender, IExternalBridgeClient machine)
        {
            MachineOperator = machine;

            if (MachineOperator != null)
            {
                MachineOperator.MachineEventsStateProvider.EventsChanged -= MachineEventsStateProvider_EventsChanged;
                MachineOperator.MachineEventsStateProvider.EventsChanged += MachineEventsStateProvider_EventsChanged;

                MachineOperator.MachineEventsStateProvider.NewEvents -= MachineEventsStateProvider_NewEvents;
                MachineOperator.MachineEventsStateProvider.NewEvents += MachineEventsStateProvider_NewEvents;
            }
        }

        private void MachineEventsStateProvider_NewEvents(object sender, IEnumerable<MachinesEvent> events)
        {
            HandleNewHardwareEvents(events);
        }

        private void MachineEventsStateProvider_EventsChanged(object sender, IEnumerable<MachinesEvent> changedEvents)
        {
            InvokeUI(StartJobCommand.RaiseCanExecuteChanged);
        }

        /// <summary>
        /// Handles the Saved event of the SelectedMachine.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
        private void SelectedMachine_Modified(object sender, ObservableModifiedEventArgs e)
        {
            if (e.IsOtherContext)
            {
                InvokeUI(() =>
                {
                    SelectedMachine.Reload(_machineDbContext);
                    InvalidateLiquidFactorsAndProcessTables();

                    if (SelectedSegment != null)
                    {
                        OnSelectedSegmentChanged();
                    }
                });
            }
        }

        /// <summary>
        /// Handles the LengthChanged event of the SelectedJob.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
        private void ActiveJob_LengthChanged(object sender, EventArgs e)
        {
            UpdateEstimatedDuration();
        }

        /// <summary>
        /// Handles the DyeingSpeedMinInkUptakeChanged event of the SelectedProcessParametersTable.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
        private void SelectedProcessParametersTable_DyeingSpeedMinInkUptakeChanged(object sender, EventArgs e)
        {
            if (SelectedSegment != null)
            {
                foreach (var liquidVolume in SelectedSegment.BrushStops.SelectMany(x => x.LiquidVolumes))
                {
                    liquidVolume.Invalidate();
                }
            }

            UpdateEstimatedDuration();
        }

        #endregion

        #region Hardware Events

        private void HandleNewHardwareEvents(IEnumerable<MachinesEvent> events)
        {
            if (IsJobRunning)
            {
                _speech.SpeakError(events.Last().EventType.Name);

                if (events.ToList().Exists(x => x.ActionTypes.Contains(BL.Enumerations.ActionTypes.AbortRunningJob)))
                {
                    if (_jobHandler != null)
                    {
                        InvokeUI(StopJob);
                    }
                }
            }
        }

        #endregion

        #region Properties Changes

        /// <summary>
        /// Called when the selected parameters table has changed.
        /// </summary>
        protected virtual void OnSelectedParametersTableChanged()
        {
            if (SelectedProcessParametersTable != null)
            {
                LogManager.Log("Selected process parameters table changed.");
                SelectedProcessParametersTable.DyeingSpeedMinInkUptakeChanged -= SelectedProcessParametersTable_DyeingSpeedMinInkUptakeChanged;
                SelectedProcessParametersTable.DyeingSpeedMinInkUptakeChanged += SelectedProcessParametersTable_DyeingSpeedMinInkUptakeChanged;

                SetSegmentBrushStopsLiquidVolumes(SelectedSegment);
                UpdateEstimatedDuration();
            }
        }

        /// <summary>
        /// Called when the process parameters table group has been changed
        /// </summary>
        protected virtual void OnProcessParametersTableGroupChanged()
        {
            if (RmlProcessParametersTableGroup != null && RmlProcessParametersTableGroup.ProcessParametersTables.Count > 0)
            {
                LogManager.Log("Process parameters group changed...");
                SelectedProcessParametersTable = RmlProcessParametersTableGroup.ProcessParametersTables.OrderBy(x => x.TableIndex).FirstOrDefault();

                UpdateEstimatedDuration();
            }
        }

        /// <summary>
        /// Called when the selected segment has been changed
        /// </summary>
        protected virtual void OnSelectedSegmentChanged()
        {
            if (SelectedSegment != null)
            {
                LogManager.Log("Selected segment changed...");
                SetSegmentBrushStopsLiquidVolumes(SelectedSegment);
                SelectedBrushStop = SelectedSegment.BrushStops.FirstOrDefault();

                BrushStopsCollectionView = CollectionViewSource.GetDefaultView(SelectedSegment.BrushStops);
                BrushStopsCollectionView.SortDescriptions.Add(new SortDescription(nameof(BrushStop.StopIndex), ListSortDirection.Ascending));
            }
        }

        /// <summary>
        /// Called when the selected group history has been changed
        /// </summary>
        protected virtual void OnSelectedGroupHistoryChanged()
        {
            if (SelectedGroupHistory != null)
            {
                LogManager.Log(String.Format("Parameters group {0} selected from history.", SelectedGroupHistory.Name));
                RmlProcessParametersTableGroup = SelectedGroupHistory.CloneGroup();
            }
        }

        /// <summary>
        /// Called when the machine has been changed
        /// </summary>
        protected virtual void OnSelectedMachineChanged()
        {
            if (SelectedMachine != null)
            {
                LogManager.Log(String.Format("Machine {0} changed.", SelectedMachine.SerialNumber));
                ReloadMachine();
                JobsCollectionView = CollectionViewSource.GetDefaultView(SelectedMachine.Jobs);
                JobsCollectionView.SortDescriptions.Add(new SortDescription(nameof(Job.LastUpdated), ListSortDirection.Descending));
            }
        }

        /// <summary>
        /// Called when the job filtering has changed.
        /// </summary>
        protected virtual void OnJobFilterChanged()
        {
            String filter = JobFilter.ToLower();

            JobsCollectionView.Filter = (job) =>
            {
                Job j = job as Job;
                return String.IsNullOrWhiteSpace(filter)
                ||
                j.Name.ToLower().Contains(filter) //Job name
                ||
                j.User.Contact.FirstName.ToLower().Contains(filter) // User first name
                ||
                j.Length.ToString().Contains(filter); //Job length
            };
        }

        /// <summary>
        /// Called when the active job has changed.
        /// </summary>
        protected virtual void OnActiveJobChanged()
        {
            if (ActiveJob != null)
            {
                LogManager.Log(String.Format("Active job {0} changed.", ActiveJob.Name));
                SegmentsCollectionView = CollectionViewSource.GetDefaultView(ActiveJob.Segments);
                SegmentsCollectionView.SortDescriptions.Add(new SortDescription(nameof(Segment.SegmentIndex), ListSortDirection.Ascending));
            }
        }

        #endregion

        #region Drag & Drop

        /// <summary>
        /// Switch the segment position in the job.
        /// </summary>
        /// <param name="dragged">The dragged.</param>
        /// <param name="dropped">The dropped.</param>
        public void OnDropSegment(Segment dragged, Segment dropped)
        {
            LogManager.Log(String.Format("Segment {0} Dropped on segment {1}", dragged.SegmentIndex, dropped.SegmentIndex));

            dragged.SegmentIndex = dropped.SegmentIndex;
            dropped.SegmentIndex++;

            int index = 1;

            foreach (var segment in ActiveJob.Segments.OrderBy(x => x.SegmentIndex))
            {
                segment.SegmentIndex = index++;
            }

            SegmentsCollectionView.Refresh();
        }

        /// <summary>
        /// Switch the brush stop position in the segment.
        /// </summary>
        /// <param name="dragged">The dragged stop.</param>
        /// <param name="dropped">The dropped stop.</param>
        public void OnDropBrushStop(BrushStop dragged, BrushStop dropped)
        {
            LogManager.Log(String.Format("BrushStop {0} Dropped on BrushStop {1}", dragged.StopIndex, dropped.StopIndex));

            dragged.SetStopIndex(dropped.StopIndex);
            dropped.SetStopIndex(dropped.StopIndex + 1);
            ArrangeBrushStopsIndices();
        }

        #endregion

        #region Running Job Management

        private void OnSelectedJobEventChanged()
        {
            if (SelectedJobEvent != null && SelectedJobEvent.Type != BL.Enumerations.EventTypes.ApplicationStarted && !_dialog_shown)
            {
                _dialog_shown = true;
                _notification.ShowModalDialog<EventDetailsViewVM, EventDetailsView>(new EventDetailsViewVM(SelectedJobEvent), (x) =>
                {

                }, () =>
                {
                    _dialog_shown = false;
                });
            }
        }

        /// <summary>
        /// Closes the job completion status bar.
        /// </summary>
        private void CloseJobCompletionStatusBar()
        {
            LogManager.Log("Closing job completion status bar...");
            _navigation.NavigateTo(DeveloperNavigationView.JobView);
            IsJobCompleted = false;
            IsJobFailed = false;
            IsJobCanceled = false;
            ShowJobStatus = false;
            RunningJob = null;
        }

        /// <summary>
        /// Stops the job.
        /// </summary>
        private void StopJob()
        {
            LogManager.Log("Stopping job...");
            IsJobRunning = false;
            IsJobCanceled = true;
            _jobHandler.Cancel();
        }

        /// <summary>
        /// Fails the job.
        /// </summary>
        private void SetJobFailed()
        {
            if (IsJobRunning)
            {
                LogManager.Log("Setting job failed state...");
                IsJobRunning = false;
                IsJobFailed = true;

                _speech.SpeakError("Job Failed!");
            }
        }

        /// <summary>
        /// Completes the job.
        /// </summary>
        private void SetJobCompleted()
        {
            LogManager.Log("Setting job completed state...");
            IsJobRunning = false;
            IsJobCompleted = true;
            _speech.SpeakInfo("Job Completed!");
        }

        /// <summary>
        /// Starts the job.
        /// </summary>
        private void StartJob()
        {
            LogManager.Log(String.Format("Starting job {0}...", ActiveJob.Name));
            if (MachineOperator == null || MachineOperator.State != TransportComponentState.Connected)
            {
                _notification.ShowError("No machine connected. Could not execute the specified job.");
                return;
            }

            if (SelectedProcessParametersTable == null)
            {
                _notification.ShowError("No process parameters table selected. Could not execute the specified job.");
                return;
            }

            foreach (var stop in ActiveJob.Segments.SelectMany(x => x.BrushStops).Where(x => x.LiquidVolumes == null))
            {
                stop.SetLiquidVolumes(SelectedMachine.Configuration, SelectedRML, SelectedProcessParametersTable);
            }

            JobEvents.Clear();
            IsJobFailed = false;
            IsJobCanceled = false;
            IsJobCompleted = false;
            IsJobRunning = true;
            ShowJobStatus = true;
            RunningJob = ActiveJob;
            _runningJobEstimatedDuration = EstimatedDuration;

            RunningJobSegments = RunningJob.EffectiveSegments.ToList();

            _navigation.NavigateTo(DeveloperNavigationView.RunningJobView);

            try
            {
                LogManager.Log("Sending job to machine operator...");

                _jobHandler = MachineOperator.Print(ActiveJob, SelectedProcessParametersTable);

                _eventLogger.Log(String.Format("Job '{0}' started...", ActiveJob.Name));

                _jobHandler.StatusChanged += (x, status) =>
                {
                    if (IsJobRunning)
                    {
                        RunningJobStatus = status;

                        if (status.Message != null)
                        {
                            _eventLogger.Log(BL.Enumerations.EventTypes.JobStatus, status.Message);
                        }
                    }
                };

                _jobHandler.SegmentStarted += (x, segment) =>
                {
                    if (!segment.IsInterSegment)
                    {
                        _speech.SpeakInfo(String.Format("Segment {0} Started.", segment.SegmentIndex));
                        _eventLogger.Log(String.Format("Segment {0} Started.", segment.SegmentIndex) + Environment.NewLine + segment.ToJsonString());
                    }
                    else
                    {
                        _speech.SpeakInfo(String.Format("Inter Segment Started."));
                        _eventLogger.Log("Inter Segment Started.");
                    }
                };

                _jobHandler.UnitCompleted += (x, unit) =>
                {
                    _speech.SpeakInfo(String.Format("{0} Units Completed.", unit + 1));
                    _eventLogger.Log(String.Format("{0} Units Completed.", unit + 1));
                };

                _jobHandler.Failed += (x, ex) =>
                {
                    LogManager.Log(ex, String.Format("Job {0} has failed.", RunningJob.Name));
                    _eventLogger.Log(ex, String.Format("Job {0} has failed.", RunningJob.Name));
                    SetJobFailed();

                    InvokeUI(() =>
                    {
                        _notification.ShowError("Job failed. " + ex.Message);
                        StopRecordingIfInProgress();
                    });
                };

                _jobHandler.Finalizing += (_, __) =>
                {
                    _speech.SpeakInfo("Finalizing job...");
                    LogManager.Log(String.Format("Finalizing job {0}.", RunningJob.Name));
                    _eventLogger.Log(String.Format("Finalizing job {0}.", RunningJob.Name));
                };

                _jobHandler.Completed += (x, e) =>
                {
                    LogManager.Log(String.Format("Job {0} has completed.", RunningJob.Name));
                    _eventLogger.Log(String.Format("Job {0} has completed.", RunningJob.Name));
                    SetJobCompleted();
                    StopRecordingIfInProgress();
                };

                _jobHandler.Canceled += (x, y) =>
                {
                    LogManager.Log(String.Format("Job {0} has been canceled.", RunningJob.Name));
                    _eventLogger.Log(String.Format("Job {0} has been canceled.", RunningJob.Name));
                    StopRecordingIfInProgress();
                    //Finally Canceled..
                };
            }
            catch (Exception ex)
            {
                LogManager.Log(ex);
                _eventLogger.Log(ex, "An error occurred while starting the job.");
                _notification.ShowError("An error occurred while starting the job. " + Environment.NewLine + ex.Message);
                SetJobFailed();
                StopRecordingIfInProgress();
            }
        }

        /// <summary>
        /// Starts the job and record using the data capture module.
        /// </summary>
        private void StartJobAndRecord()
        {
            _isRecording = true;
            _dataCaptureVM.StartDiagnosticsRecording();
            StartJob();
        }

        /// <summary>
        /// Stops the recording if in progress.
        /// </summary>
        private void StopRecordingIfInProgress()
        {
            if (_isRecording)
            {
                _isRecording = false;
                InvokeUI(() => _dataCaptureVM.StopRecorderOrPlayer());
            }
        }

        #endregion

        #region RML

        /// <summary>
        /// Saves the liquid factors.
        /// </summary>
        private async void SaveLiquidFactors()
        {
            if (SelectedRML != null)
            {
                using (_notification.PushTaskItem("Saving Liquid Factors..."))
                {
                    LogManager.Log(String.Format("Saving liquid factors for RML {0}...", SelectedRML.Name));
                    await SelectedRML.SaveAsync(_activeJobDbContext);
                    InvalidateLiquidFactorsAndProcessTables();
                }
            }
        }

        /// <summary>
        /// Navigates to the DB Module in order to edit the selected RML.
        /// </summary>
        private void EditRML()
        {
            LogManager.Log(String.Format("Requesting DB module for RML {0} editing...", SelectedRML.Name));
            ApplicationManager.RequestModule("Data Base", SelectedRML);
        }

        /// <summary>
        /// Invalidates the liquid factors and process parameters tables.
        /// </summary>
        private void InvalidateLiquidFactorsAndProcessTables()
        {
            if (SelectedRML != null && SelectedMachine != null)
            {
                LogManager.Log("Invalidating liquid factors, process parameters and process group history...");
                LiquidTypesRmls = SelectedMachine.Configuration.NoneEmptyIdsPacks.Where(x => !x.IsEmpty).OrderBy(x => x.PackIndex).Select(x => x.LiquidType).SelectMany(x => x.LiquidTypesRmls).Where(x => x.Rml.Guid == SelectedRML.Guid).ToList();
                RmlProcessParametersTableGroup = SelectedRML.ProcessParametersTablesGroups.ToList().SingleOrDefault(x => x.Active);

                var selectedHistory = RmlProcessParametersTableGroup;

                if (RmlProcessParametersTableGroup != null)
                {
                    RmlProcessParametersTableGroup = RmlProcessParametersTableGroup.CloneGroup();
                    RmlProcessParametersTableGroup.ProcessParametersTables = RmlProcessParametersTableGroup.ProcessParametersTables.OrderBy(x => x.TableIndex).ToObservableCollection();
                }

                GroupsHistory = SelectedRML.ProcessParametersTablesGroups.OrderByDescending(x => x.SaveDate).OrderBy(x => !x.Active).ToObservableCollection();

                _selectedGroupHistory = selectedHistory;

                RaisePropertyChangedAuto(nameof(SelectedGroupHistory));
            }
        }

        #endregion

        #region Process Parameters Management

        /// <summary>
        /// Uploads the selected process parameters table.
        /// </summary>
        private async void PushProcessParameters()
        {
            using (_notification.PushTaskItem("Uploading Process Parameters..."))
            {
                try
                {
                    LogManager.Log(String.Format("Uploading process parameters table {0}...", SelectedProcessParametersTable.Name));
                    await MachineOperator.UploadProcessParameters(SelectedProcessParametersTable);
                }
                catch (Exception ex)
                {
                    LogManager.LogFormat(ex, "Failed to upload process parameters table {0}", SelectedProcessParametersTable.Name);
                    _notification.ShowError("Failed to upload the selected process parameters." + Environment.NewLine + ex.Message);
                }
            }
        }

        /// <summary>
        /// Saves the process parameters group.
        /// </summary>
        private async void SaveProcessParameters()
        {
            var response = _notification.ShowTextInput("Enter Group Name", "Group Name");

            if (response == null) return;

            using (_notification.PushTaskItem("Saving Parameters Group..."))
            {
                LogManager.Log(String.Format("Saving process parameters group under the name {0}...", response));
                ProcessParametersTablesGroup group = new ProcessParametersTablesGroup();

                List<ProcessParametersTable> tables = new List<ProcessParametersTable>();

                int index = 0;

                foreach (var table in RmlProcessParametersTableGroup.ProcessParametersTables)
                {
                    var newTable = table.CloneEntity();
                    newTable.TableIndex = index++;
                    newTable.ProcessParametersTablesGroup = group;
                    tables.Add(newTable);
                }

                group.Active = true;
                group.ProcessParametersTables = tables.ToObservableCollection();
                group.Rml = SelectedRML;
                group.Name = response;
                group.SaveDate = DateTime.UtcNow;

                foreach (var g in SelectedRML.ProcessParametersTablesGroups)
                {
                    g.Active = false;
                }

                SelectedRML.ProcessParametersTablesGroups.Add(group);
                await SelectedRML.SaveAsync(_activeJobDbContext);

                InvalidateLiquidFactorsAndProcessTables();
            }
        }

        #endregion

        #region Active Job Management

        /// <summary>
        /// Loads the selected job.
        /// </summary>
        private void LoadSelectedJob()
        {
            if (SelectedMachineJob != null)
            {
                using (_notification.PushTaskItem("Loading job details..."))
                {
                    LogManager.Log(String.Format("Loading job {0}...", SelectedMachineJob.Name));
                    SelectedSegments = new ObservableCollection<Segment>();
                    SelectedBrushStops = new ObservableCollection<BrushStop>();
                    SelectedRML = null;
                    SelectedSegment = null;
                    SelectedGroupHistory = null;
                    SelectedBrushStop = null;
                    SelectedProcessParametersTable = null;
                    RmlProcessParametersTableGroup = null;


                    _blockInvalidateCommands = false;

                    LogManager.Log("Creating active job DB context...");
                    _activeJobDbContext = ObservablesContext.CreateDefault();
                    _activeJobDbContext.Configuration.LazyLoadingEnabled = true;

                    LogManager.Log("Initializing available color spaces, RMLs & Winding methods...");
                    var processParamsGroups = _activeJobDbContext.ProcessParametersTablesGroups.ToList();
                    var processParams = _activeJobDbContext.ProcessParametersTables.ToList();
                    ColorSpaces = _activeJobDbContext.ColorSpaces.ToObservableCollection();
                    Rmls = _activeJobDbContext.Rmls.ToObservableCollection();
                    WindingMethods = _activeJobDbContext.WindingMethods.ToObservableCollection();
                    SpoolTypes = _activeJobDbContext.SpoolTypes.ToObservableCollection();


                    LogManager.Log("Setting active job...");
                    _activeJob = _activeJobDbContext.Jobs.SingleOrDefault(x => x.Guid == SelectedMachineJob.Guid);

                    _selectedRML = ActiveJob.Rml;

                    LogManager.Log("Setting selected segment...");
                    _selectedSegment = ActiveJob.Segments.FirstOrDefault();

                    ActiveJob.LengthChanged -= ActiveJob_LengthChanged;
                    ActiveJob.LengthChanged += ActiveJob_LengthChanged;

                    ActiveJob = _activeJob;

                    SelectedRML = _selectedRML;
                    SelectedSegment = _selectedSegment;

                    UpdateEstimatedDuration();

                    _blockInvalidateCommands = false;
                    InvalidateRelayCommands();

                    UIHelper.DoEvents();
                    _navigation.NavigateTo(DeveloperNavigationView.JobView);

                }
            }
        }

        /// <summary>
        /// Saves the active job.
        /// </summary>
        private void SaveActiveJob()
        {
            if (ActiveJob != null)
            {
                using (_notification.PushTaskItem("Saving job details..."))
                {
                    LogManager.Log(String.Format("Saving the active job {0}...", ActiveJob.Name));
                    ActiveJob.LastUpdated = DateTime.UtcNow;
                    ActiveJob.Rml = SelectedRML;
                    ActiveJob.EstimatedDurationMili = (int)EstimatedDuration.TotalMilliseconds;

                    _activeJobDbContext.SaveChanges();
                    ReloadMachine();
                    SelectedMachineJob = SelectedMachine.Jobs.SingleOrDefault(x => x.Guid == ActiveJob.Guid);
                }
            }
        }

        private void BackToJobs()
        {
            LogManager.Log("User request for 'back to jobs'...");
            LogManager.Log("Comparing active job with selected job...");

            bool jobModified = !ActiveJob.CompareUsingJson(SelectedMachineJob);

            if (jobModified)
            {
                LogManager.Log("Selected job has been modified. Invoking confirmation dialog...");
                if (_notification.ShowQuestion("This will discard the current job changes. Are you sure?"))
                {
                    LogManager.Log("Disposing active job db context...");
                    _activeJobDbContext.Dispose();
                    _navigation.NavigateTo(DeveloperNavigationView.MachineJobSelectionView);
                }
            }
            else
            {
                LogManager.Log("Disposing active job db context...");
                _activeJobDbContext.Dispose();
                _navigation.NavigateTo(DeveloperNavigationView.MachineJobSelectionView);
            }
        }

        #endregion

        #region Private Methods

        private void ReloadMachine()
        {
            LogManager.Log("Reloading selected machine...");
            _machineDbContext.Dispose();
            _machineDbContext = ObservablesContext.CreateDefault();
            _machineDbContext.Configuration.LazyLoadingEnabled = true;
            String machineGuid = _selectedMachine.Guid;
            Machines = _machineDbContext.Machines.ToObservableCollection();
            _selectedMachine = Machines.SingleOrDefault(x => x.Guid == machineGuid);
            RaisePropertyChanged(nameof(SelectedMachine));

            JobsCollectionView = CollectionViewSource.GetDefaultView(SelectedMachine.Jobs);
            JobsCollectionView.SortDescriptions.Add(new SortDescription(nameof(Job.LastUpdated), ListSortDirection.Descending));
        }

        private void UpdateEstimatedDuration()
        {
            if (ActiveJob != null && SelectedProcessParametersTable != null && SelectedProcessParametersTable.DyeingSpeed > 0)
            {
                EstimatedDuration = ActiveJob.GetEstimatedDuration(SelectedProcessParametersTable);
            }
        }

        private void SetSegmentBrushStopsLiquidVolumes(Segment segment)
        {
            if (!DesignMode && segment != null)
            {
                LogManager.Log("Setting segment brush stops liquid volumes...");
                foreach (var stop in segment.BrushStops)
                {
                    stop.SetLiquidVolumes(SelectedMachine.Configuration, SelectedRML, SelectedProcessParametersTable);
                }
            }
        }

        /// <summary>
        /// Navigates to the Machine Designer Module in order to edit the selected machine.
        /// </summary>
        private void EditMachine()
        {
            LogManager.Log(String.Format("Requesting machine designer module for machine {0} editing...", SelectedMachine.SerialNumber));
            ApplicationManager.RequestModule("Machine Designer", SelectedMachine);
        }

        #endregion

        #region Add / Remove / Duplicate Jobs, Segments & Brush Stops

        /// <summary>
        /// Arranges the segments indices.
        /// </summary>
        private void ArrangeSegmentsIndices()
        {
            int index = 1;

            foreach (var segment in ActiveJob.Segments.OrderBy(x => x.SegmentIndex))
            {
                segment.SegmentIndex = index++;
            }

            SegmentsCollectionView.Refresh();
        }

        /// <summary>
        /// Arranges the brush stops indices.
        /// </summary>
        private void ArrangeBrushStopsIndices()
        {
            int index = 0;

            foreach (var stop in SelectedSegment.BrushStops.OrderBy(x => x.StopIndex))
            {
                stop.SetStopIndex(index++);
            }

            if (SelectedSegment.BrushStops.Count > 1)
            {
                SelectedSegment.BrushStops.OrderBy(x => x.StopIndex).First().OffsetPercent = 0;
                SelectedSegment.BrushStops.OrderBy(x => x.StopIndex).Last().OffsetPercent = 100;
            }

            foreach (var stop in SelectedSegment.BrushStops.OrderBy(x => x.StopIndex))
            {
                stop.RaiseStopIndex();
                stop.RaiseOffsetChanged();
            }

            BrushStopsCollectionView.Refresh();
        }

        /// <summary>
        /// Removes the selected segments.
        /// </summary>
        private void RemoveSelectedSegments()
        {
            if (ActiveJob != null && SelectedSegment != null)
            {
                if (_notification.ShowQuestion("Are you sure you want to delete the selected segments?"))
                {
                    LogManager.Log(String.Format("Removing {0} segments...", SelectedSegments.Count));

                    SelectedSegments.ToList().ForEach(x =>
                    {
                        ActiveJob.Segments.Remove(x);
                        x.DefferedDelete(_activeJobDbContext);
                    });

                    ArrangeSegmentsIndices();
                }
            }
        }

        /// <summary>
        /// Adds a new segment.
        /// </summary>
        private void AddSegment()
        {
            if (ActiveJob != null)
            {
                LogManager.LogFormat("Adding new segment to job {0}...", ActiveJob.Name);
                Segment seg = new Segment();
                seg.Name = "Untitled Segment";
                seg.Length = 10;

                if (ActiveJob.Segments.Count > 0)
                {
                    seg.SegmentIndex = ActiveJob.Segments.Max(x => x.SegmentIndex) + 1;
                }
                else
                {
                    seg.SegmentIndex = 1;
                }

                ActiveJob.Segments.Add(seg);
                SelectedSegment = seg;
                AddBrushStop();
                ArrangeSegmentsIndices();
            }
        }

        /// <summary>
        /// Removes the selected jobs.
        /// </summary>
        private async void RemoveSelectedJobs()
        {
            if (SelectedMachine != null && SelectedMachineJob != null)
            {
                if (_notification.ShowQuestion("Are you sure you want to delete the selected jobs?"))
                {
                    LogManager.Log(String.Format("Removing {0} jobs...", SelectedJobs.Count));
                    SelectedJobs.ToList().ForEach(x =>
                    {
                        SelectedMachine.Jobs.Remove(x);
                        x.DefferedDelete(_machineDbContext);
                    });

                    using (_notification.PushTaskItem("Removing selected jobs..."))
                    {
                        LogManager.Log("Saving selected machine to database...");
                        await SelectedMachine.SaveAsync(_machineDbContext);
                    }
                }
            }
        }

        /// <summary>
        /// Adds a new job to the selected machine.
        /// </summary>
        private async void AddJob()
        {
            if (SelectedMachine != null)
            {
                String jobName = _notification.ShowTextInput("Please provide a job name", "Name");

                if (!String.IsNullOrWhiteSpace(jobName))
                {
                    LogManager.Log(String.Format("Adding new job {0}...", jobName));

                    Job newJob = new Job();
                    newJob.Name = jobName;
                    newJob.CreationDate = DateTime.UtcNow;
                    newJob.UserGuid = _authentication.CurrentUser.Guid;
                    newJob.Rml = _machineDbContext.Rmls.FirstOrDefault();
                    newJob.WindingMethod = _machineDbContext.WindingMethods.FirstOrDefault();
                    newJob.SpoolType = _machineDbContext.SpoolTypes.FirstOrDefault();
                    newJob.Machine = SelectedMachine;

                    SelectedMachine.Jobs.Add(newJob);
                    LogManager.Log("Saving selected machine to database...");
                    await SelectedMachine.SaveAsync(_machineDbContext);
                    SelectedMachineJob = newJob;
                    LoadSelectedJob();
                    AddSegment();
                }
            }
        }

        /// <summary>
        /// Removes the selected brush stop.
        /// </summary>
        private void RemoveSelectedBrushStops()
        {
            if (SelectedBrushStop != null && SelectedSegment != null)
            {
                if (_notification.ShowQuestion("Are you sure you want to delete the selected colors?"))
                {
                    LogManager.Log(String.Format("Removing {0} brush stops...", SelectedBrushStops.Count));

                    SelectedBrushStops.ToList().ForEach(x =>
                    {
                        SelectedSegment.BrushStops.Remove(x);
                        x.DefferedDelete(_activeJobDbContext);
                    });

                    ArrangeBrushStopsIndices();
                }
            }
        }

        /// <summary>
        /// Adds a new brush stop to the selected segment.
        /// </summary>
        private void AddBrushStop()
        {
            if (SelectedSegment != null)
            {
                LogManager.LogFormat("Adding new brush stop to segment...", SelectedSegment.SegmentIndex.ToString());

                var stop = new BrushStop();

                if (SelectedSegment.BrushStops.Count > 0)
                {
                    stop.StopIndex = SelectedSegment.BrushStops.Max(x => x.StopIndex) + 1;
                }
                else
                {
                    stop.StopIndex = 1;
                }

                stop.OffsetPercent = 100;
                stop.Segment = SelectedSegment;
                stop.ColorSpace = _activeJobDbContext.ColorSpaces.FirstOrDefault();
                stop.Color = Colors.Black;
                stop.SetLiquidVolumes(SelectedMachine.Configuration, SelectedRML, SelectedProcessParametersTable);
                SelectedSegment.BrushStops.Add(stop);
                SelectedSegment.BrushStops.ToList().ForEach(x => x.RaiseOffsetChanged());
                ArrangeBrushStopsIndices();
            }
        }

        /// <summary>
        /// Duplicates the selected brush stops.
        /// </summary>
        private void DuplicateSelectedBrushStops()
        {
            LogManager.LogFormat("Duplicating {0} brush stops...", SelectedBrushStops.Count);

            foreach (var stop in SelectedBrushStops.OrderBy(x => x.StopIndex))
            {
                var cloned = stop.Clone();
                cloned.StopIndex = SelectedSegment.BrushStops.Max(x => x.StopIndex) + 1;
                SelectedSegment.BrushStops.Add(cloned);
            }

            ArrangeBrushStopsIndices();
        }

        /// <summary>
        /// Duplicates the selected segments.
        /// </summary>
        private void DuplicateSelectedSegments()
        {
            LogManager.LogFormat("Duplicating {0} segments...", SelectedSegments.Count);

            foreach (var segment in SelectedSegments.OrderBy(x => x.SegmentIndex))
            {
                var cloned = segment.Clone();
                cloned.SegmentIndex = ActiveJob.Segments.Max(x => x.SegmentIndex) + 1;
                ActiveJob.Segments.Add(cloned);
            }

            ArrangeSegmentsIndices();
        }

        /// <summary>
        /// Duplicates the selected jobs.
        /// </summary>
        private async void DuplicateSelectedJobs()
        {
            if (SelectedMachineJob != null)
            {
                using (_notification.PushTaskItem("Cloning selected jobs..."))
                {
                    LogManager.LogFormat("Duplicating {0} jobs...", SelectedJobs.Count);

                    int index = SelectedMachine.Jobs.Max(x => x.JobIndex);

                    foreach (var job in SelectedJobs)
                    {
                        var cloned = job.Clone();
                        cloned.JobIndex = ++index;
                        SelectedMachine.Jobs.Add(cloned);
                    }

                    LogManager.Log("Saving selected machine to database...");
                    await SelectedMachine.SaveAsync(_machineDbContext);
                }
            }
        }

        #endregion

        #region Embroidery

        /// <summary>
        /// Imports embroidery file.
        /// </summary>
        private void ImportEmbroideryFile()
        {
            OpenFileDialog dlg = new OpenFileDialog();
            dlg.Title = "Select embroidery file";
            dlg.Filter = EMB_FORMATS_IMPORT;
            if (dlg.ShowDialogCenter())
            {
                var view = new EmbroideryImportView();

                _notification.ShowModalDialog<EmbroideryImportViewVM, EmbroideryImportView>(
                    new EmbroideryImportViewVM() { FileName = dlg.FileName }, view,
                    (vm) =>
                    {
                        String jobName = _notification.ShowTextInput("Please provide a job name", "Name");

                        if (jobName != null)
                        {
                            AddJobFromEmbroideryFile(jobName, vm, dlg.FileName, view.EmbroideryImageBytes);
                        }
                    },
                    () =>
                    {

                    });
            }
        }

        private async void AddJobFromEmbroideryFile(String jobName, EmbroideryImportViewVM vm, String fileName, byte[] imageBytes)
        {
            LogManager.Log(String.Format("Adding new job from embroidery file {0}...", jobName));

            Job job = new Job();
            job.Name = jobName;
            job.Name = jobName;
            job.CreationDate = DateTime.UtcNow;
            job.UserGuid = _authentication.CurrentUser.Guid;
            job.Rml = _machineDbContext.Rmls.FirstOrDefault();
            job.WindingMethod = _machineDbContext.WindingMethods.FirstOrDefault();
            job.SpoolType = _machineDbContext.SpoolTypes.FirstOrDefault();
            job.Machine = SelectedMachine;

            job.EmbroideryFileName = Path.GetFileName(fileName);
            job.EmbroideryFileData = File.ReadAllBytes(fileName);
            job.EmbroideryJpeg = imageBytes;
            job.HasEmbroideryFile = true;

            foreach (var path in vm.Paths.Skip(1))
            {
                Segment segment = new Segment();

                double baseLength = path.Length / 1000d;
                double embThicknessLength = (vm.EmbroideryMaterialThickness * path.StitchCount) / 1000d;
                double stabilizerThicknessLength = (vm.StabilizerThickness * path.StitchCount) / 1000d;
                double totalLength = (baseLength + embThicknessLength) * vm.SelectedEmbroideryMaterial.Coefficient;

                if (vm.HasStabilizer)
                {
                    totalLength += (stabilizerThicknessLength * vm.SelectedStabilizer.Coefficient);
                }

                segment.Length = totalLength;
                segment.Name = "Embroidery Segment";
                segment.SegmentIndex = vm.Paths.IndexOf(path);

                if (path.Brush is SolidColorBrush)
                {
                    var brush = (path.Brush as SolidColorBrush);

                    segment.BrushStops.Add(new BrushStop()
                    {
                        Red = brush.Color.R,
                        Green = brush.Color.G,
                        Blue = brush.Color.B,
                        ColorSpace = _machineDbContext.ColorSpaces.ToList().SingleOrDefault(x => x.Code == BL.Enumerations.ColorSpaces.RGB.ToInt32()),
                    });
                }
                else
                {
                    var brush = (path.Brush as LinearGradientBrush);

                    foreach (var stop in brush.GradientStops)
                    {
                        segment.BrushStops.Add(new BrushStop()
                        {
                            StopIndex = brush.GradientStops.IndexOf(stop),
                            Red = stop.Color.R,
                            Green = stop.Color.G,
                            Blue = stop.Color.B,
                            OffsetPercent = stop.Offset * 100d,
                            ColorSpace = _machineDbContext.ColorSpaces.ToList().SingleOrDefault(x => x.Code == BL.Enumerations.ColorSpaces.RGB.ToInt32()),
                        });
                    }
                }

                job.Segments.Add(segment);
            }

            SelectedMachine.Jobs.Add(job);
            LogManager.Log("Saving selected machine to database...");
            await SelectedMachine.SaveAsync(_machineDbContext);
            SelectedMachineJob = job;
            LoadSelectedJob();
        }

        private void DisplayJobEmbroideryFile(Job job)
        {
            _notification.ShowModalDialog<EmbroideryDisplayViewVM, EmbroideryDisplayView>(new EmbroideryDisplayViewVM(job), (vm) =>
            {

                SaveFileDialog dlg = new SaveFileDialog();
                dlg.Title = "Select embroidery file location and format";
                dlg.Filter = EMB_FORMATS_EXPORT;
                dlg.FileName = job.EmbroideryFileName;
                if (dlg.ShowDialogCenter())
                {
                    try
                    {
                        var tempDir = TemporaryManager.CreateFolder();
                        String filePath = Path.Combine(tempDir.Path, job.EmbroideryFileName);
                        File.WriteAllBytes(filePath, job.EmbroideryFileData);
                        EmbroideryFileConverter.ConvertEmbroideryFile(filePath, dlg.FileName);
                        _notification.ShowInfo("Embroidery file exported successfully.");
                    }
                    catch (Exception ex)
                    {
                        LogManager.Log(ex, "An error has occurred while trying to export the attached embroidery file.");
                        _notification.ShowError("An error has occurred while trying to export the attached embroidery file.");
                    }
                }

            }, () => { });
        }

        #endregion

        #region Override Methods

        protected override void RaisePropertyChangedAuto([CallerMemberName] string caller = null)
        {
            base.RaisePropertyChangedAuto(caller);

            if (!_blockInvalidateCommands)
            {
                InvalidateRelayCommands();
            }
        }

        #endregion

        #region IStudioViewModel

        public override Task<bool> OnShutdownRequest()
        {
            if (IsJobRunning)
            {
                InvokeUI(() =>
                {
                    _notification.ShowWarning("Please stop the currently running job before closing the developer module.");
                });

                return Task.FromResult(false);
            }

            return Task.FromResult(true);
        }

        public override void OnShuttingDown()
        {
            _settings.LastSelectedMachineGuid = SelectedMachine != null ? SelectedMachine.Guid : null;
            _settings.LastSelectedJobGuid = SelectedMachineJob != null ? SelectedMachineJob.Guid : null;
        }

        #endregion
    }
}