aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.Technician/ViewModels/MachineTechViewVM.cs
blob: 8f5c67f969dfb674bb772b9d1f2ebb652fa44be0 (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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data.Entity;
using Tango.BL;
using Tango.MachineStudio.Common;
using Tango.MachineStudio.MachineManager.Models;
using Tango.BL.Entities;
using Tango.Core.Commands;
using Tango.MachineStudio.Common.Notifications;
using Z.EntityFramework.Plus;
using System.ComponentModel;
using Tango.BL.Enumerations;

namespace Tango.MachineStudio.MachineManager.ViewModels
{
    public class MainViewVM : StudioViewModel
    {
        private INotificationProvider _notification;
        private bool _preventChange;

        private List<MachineModel> _machines;
        public List<MachineModel> Machines
        {
            get { return _machines; }
            set { _machines = value; RaisePropertyChangedAuto(); }
        }

        private String _filter;
        public String Filter
        {
            get { return _filter; }
            set
            {
                if (_filter != value)
                {
                    _filter = value;
                    OnFilterChanged();
                }
            }
        }

        private List<HardwareVersion> _hardwareVersions;
        public List<HardwareVersion> HardwareVersions
        {
            get { return _hardwareVersions; }
            set { _hardwareVersions = value; RaisePropertyChangedAuto(); }
        }

        private HardwareVersion _selectedHardwareVersion;
        public HardwareVersion SelectedHardwareVersion
        {
            get { return _selectedHardwareVersion; }
            set { _selectedHardwareVersion = value; RaisePropertyChangedAuto(); InvalidateRelayCommands(); }
        }

        private bool? _enableSoftwareUpdate;
        public bool? EnableSoftwareUpdate
        {
            get { return _enableSoftwareUpdate; }
            set { _enableSoftwareUpdate = value; RaisePropertyChangedAuto(); InvalidateRelayCommands(); }
        }

        private bool? _enableFirmwareUpgrade;
        public bool? EnableFirmwareUpgrade
        {
            get { return _enableFirmwareUpgrade; }
            set { _enableFirmwareUpgrade = value; RaisePropertyChangedAuto(); InvalidateRelayCommands(); }
        }

        private bool? _selectAll;
        public bool? SelectAll
        {
            get { return _selectAll; }
            set { _selectAll = value; RaisePropertyChangedAuto(); OnSelectAllChanged(); InvalidateRelayCommands(); }
        }

        public RelayCommand ApplyCommand { get; set; }

        public MainViewVM(INotificationProvider notificationProvider)
        {
            Machines = new List<MachineModel>();
            _notification = notificationProvider;
            ApplyCommand = new RelayCommand(Apply, CanApply);
        }

        public override void OnApplicationReady()
        {

        }

        private async void OnFilterChanged()
        {
            await LoadMachines();
        }

        private void OnSelectAllChanged()
        {
            if (Machines != null && SelectAll != null)
            {
                _preventChange = true;
                Machines.ForEach(x => x.IsSelected = SelectAll.Value);
                _preventChange = false;
            }
        }

        private async Task LoadMachines()
        {
            try
            {
                using (_notification.PushTaskItem("Loading machines..."))
                {
                    SelectAll = null;

                    foreach (var machine in Machines.ToList())
                    {
                        machine.PropertyChanged -= Machine_PropertyChanged;
                    }

                    using (ObservablesContext db = ObservablesContext.CreateDefault())
                    {
                        var sites = await db.Sites.ToListAsync();

                        var machines = await db.Machines
                            .Include(x => x.Organization.Name)
                            .Include(x => x.Configuration.HardwareVersion.Name)
                            .Include(x => x.Configuration.HardwareVersion.Version)
                            .Include(x => x.Configuration.HardwareVersion.Guid)
                            .Select(x => new
                            {
                                x.Guid,
                                x.SerialNumber,
                                x.Name,
                                x.SuspendVersionUpdate,
                                x.SetupFirmware,
                                x.SetupFpga,
                                x.ConfigurationGuid,
                                HardwareVersionGuid = x.Configuration.HardwareVersion.Guid,
                                HardwareVersionName = x.Configuration.HardwareVersion.Name,
                                HardwareVersionVersion = x.Configuration.HardwareVersion.Version,
                                OrganizationName = x.Organization.Name,
                                x.SiteGuid,
                                x.VersionTag
                            }).ToListAsync();

                        var models = new List<MachineModel>();

                        foreach (var machine in machines)
                        {
                            MachineModel model = new MachineModel();
                            model.Guid = machine.Guid;
                            model.SerialNumber = machine.SerialNumber;
                            model.Name = machine.Name;
                            model.EnableFirmwareUpgrade = machine.SetupFirmware;
                            model.EnableSoftwareUpdate = !machine.SuspendVersionUpdate;
                            model.ConfigurationGuid = machine.ConfigurationGuid;
                            model.HardwareVersionGuid = machine.HardwareVersionGuid;
                            model.HardwareVersionName = machine.HardwareVersionName;
                            model.HardwareVersionVersion = machine.HardwareVersionVersion;
                            model.OrganizationName = machine.OrganizationName;
                            model.VersionTag = machine.VersionTag;
                            model.Site = sites.FirstOrDefault(x => x.Guid == machine.SiteGuid)?.Name;
                            models.Add(model);
                        }

                        if (Filter.IsNotNullOrEmpty())
                        {
                            var filter = Filter.ToLower();

                            models = models.Where(x =>
                            x.SerialNumber.ToLower().StartsWith(filter) ||
                            x.Name.ToLower().StartsWith(filter) ||
                            x.OrganizationName.ToLower().StartsWith(filter) ||
                            x.Site.ToStringOrEmpty().ToLower().StartsWith(filter) ||
                            x.VersionTag.ToStringOrEmpty().ToLower().StartsWith(filter)
                            ).ToList();
                        }

                        foreach (var model in models)
                        {
                            var lastUpdate = await db.TangoUpdates.Where(

                            x => x.MachineGuid == model.Guid &&
                            (x.Status == (int)TangoUpdateStatuses.SetupCompleted ||
                            x.Status == (int)TangoUpdateStatuses.UpdateCompleted ||
                            x.Status == (int)TangoUpdateStatuses.OfflineUpdateCompleted ||
                            x.Status == (int)TangoUpdateStatuses.OfflineFirmwareUpgradeCompleted))
                            .OrderByDescending(x => x.LastUpdated)
                            .Select(x => new
                            {
                                x.ApplicationVersion,
                                x.FirmwareVersion
                            })
                            .FirstOrDefaultAsync();

                            if (lastUpdate != null)
                            {
                                model.ApplicationVersion = lastUpdate.ApplicationVersion;
                                model.FirmwareVersion = lastUpdate.FirmwareVersion;
                            }
                        }

                        HardwareVersions = (await db.HardwareVersions.ToListAsync()).OrderByDescending(x => x.Version).ToList();

                        SelectedHardwareVersion = null;
                        EnableSoftwareUpdate = null;
                        EnableFirmwareUpgrade = null;

                        Machines = models;

                        foreach (var machine in Machines.ToList())
                        {
                            machine.PropertyChanged += Machine_PropertyChanged;
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                LogManager.Log(ex, "Error loading machines.");
                _notification.ShowError($"Error loading machines.\n{ex.FlattenMessage()}");
            }
        }

        private void Machine_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (_preventChange) return;

            if (e.PropertyName == nameof(MachineModel.IsSelected))
            {
                if (Machines.TrueForAll(x => x.IsSelected))
                {
                    _selectAll = true;
                }
                else if (Machines.TrueForAll(x => !x.IsSelected))
                {
                    _selectAll = false;
                }
                else
                {
                    _selectAll = null;
                }

                RaisePropertyChanged(nameof(SelectAll));

                var selectedMachines = Machines.Where(x => x.IsSelected).ToList();

                if (selectedMachines.Count > 0)
                {
                    if (selectedMachines.TrueForAll(x => x.EnableSoftwareUpdate))
                    {
                        EnableSoftwareUpdate = true;
                    }
                    else if (selectedMachines.TrueForAll(x => !x.EnableSoftwareUpdate))
                    {
                        EnableSoftwareUpdate = false;
                    }
                    else
                    {
                        EnableSoftwareUpdate = null;
                    }

                    if (selectedMachines.TrueForAll(x => x.EnableFirmwareUpgrade))
                    {
                        EnableFirmwareUpgrade = true;
                    }
                    else if (selectedMachines.TrueForAll(x => !x.EnableFirmwareUpgrade))
                    {
                        EnableFirmwareUpgrade = false;
                    }
                    else
                    {
                        EnableFirmwareUpgrade = null;
                    }

                    if (selectedMachines.TrueForAll(x => x.HardwareVersionGuid == selectedMachines.First().HardwareVersionGuid))
                    {
                        SelectedHardwareVersion = HardwareVersions.FirstOrDefault(x => x.Guid == selectedMachines.First().HardwareVersionGuid);
                    }
                    else
                    {
                        SelectedHardwareVersion = null;
                    }
                }

                InvalidateRelayCommands();
            }
        }

        private bool CanApply()
        {
            var selectedMachines = Machines.Where(x => x.IsSelected).ToList();

            if (selectedMachines.Count == 0) return false;
            if (EnableSoftwareUpdate != null && selectedMachines.Any(x => x.EnableSoftwareUpdate != EnableSoftwareUpdate)) return true;
            if (EnableFirmwareUpgrade != null && selectedMachines.Any(x => x.EnableFirmwareUpgrade != EnableFirmwareUpgrade)) return true;
            if (SelectedHardwareVersion != null && selectedMachines.Any(x => x.HardwareVersionGuid != SelectedHardwareVersion.Guid)) return true;

            return false;
        }

        private async void Apply()
        {
            if (!_notification.ShowQuestion($"You are about to modify {Machines.Count(x => x.IsSelected)} machines. Are you sure?")) return;

            try
            {
                IsFree = false;

                using (_notification.PushTaskItem("Applying changes..."))
                {
                    if (Machines == null || Machines.Count(x => x.IsSelected) == 0)
                    {
                        _notification.ShowInfo("No machines selected");
                        return;
                    }

                    using (ObservablesContext db = ObservablesContext.CreateDefault())
                    {
                        var machines = Machines.Where(x => x.IsSelected).ToList();

                        var guids = machines.Select(x => x.Guid).ToArray();

                        if (EnableSoftwareUpdate != null)
                        {
                            await db.Machines.Where(x => guids.Contains(x.Guid)).UpdateAsync(x =>
                            new Machine()
                            {
                                SuspendVersionUpdate = !EnableSoftwareUpdate.Value
                            });
                        }

                        if (EnableFirmwareUpgrade != null)
                        {
                            await db.Machines.Where(x => guids.Contains(x.Guid)).UpdateAsync(x =>
                            new Machine()
                            {
                                SetupFirmware = EnableFirmwareUpgrade.Value,
                                SetupFpga = EnableFirmwareUpgrade.Value,
                            });
                        }

                        if (SelectedHardwareVersion != null)
                        {
                            guids = machines.Select(x => x.ConfigurationGuid).ToArray();

                            await db.Configurations.Where(x => guids.Contains(x.Guid)).UpdateAsync(x =>
                            new Configuration()
                            {
                                HardwareVersionGuid = SelectedHardwareVersion.Guid
                            });
                        }
                    }

                    await LoadMachines();
                }
            }
            catch (Exception ex)
            {
                LogManager.Log(ex, "Error applying changes.");
                _notification.ShowError($"Error applying changes.\n{ex.FlattenMessage()}");
            }
            finally
            {
                IsFree = true;
            }
        }
    }
}
2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 2619 2620 2621 2622 2623 2624 2625 2626 2627 2628 2629 2630 2631 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650 2651 2652 2653 2654 2655 2656 2657 2658 2659 2660 2661 2662 2663 2664 2665 2666 2667 2668 2669 2670 2671 2672 2673 2674 2675 2676 2677 2678 2679 2680 2681 2682 2683 2684 2685 2686 2687 2688 2689 2690 2691 2692 2693 2694 2695 2696 2697 2698 2699 2700 2701 2702 2703 2704 2705 2706 2707 2708
using Google.Protobuf.Collections;
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using Tango.Core.Helpers;
using Tango.Editors;
using Tango.BL.Entities;
using Tango.Integration.Operation;
using Tango.MachineStudio.Common.Diagnostics;
using Tango.MachineStudio.Common.Notifications;
using Tango.MachineStudio.Common.StudioApplication;
using Tango.MachineStudio.Technician.Editors;
using Tango.MachineStudio.Technician.Project;
using Tango.MachineStudio.Technician.TechItems;
using Tango.PMR.Diagnostics;
using Tango.Settings;
using Tango.SharedUI;
using Tango.Integration.ExternalBridge;
using Tango.BL.Enumerations;
using Tango.BL;
using Tango.MachineStudio.Common.EventLogging;
using Tango.MachineStudio.Common;
using Tango.Core.Commands;
using Tango.MachineStudio.Technician.Helpers;
using Tango.MachineStudio.Technician.Models;
using Tango.Logging;
using Microsoft.WindowsAPICodePack.Dialogs;
using RealTimeGraphX;
using RealTimeGraphX.DataPoints;
using Tango.MachineStudio.Technician.Views;
using RealTimeGraphX.WPF;
using Tango.Core.ExtensionMethods;
using System.Diagnostics;
using Tango.BL.Builders;
using Tango.Core;

namespace Tango.MachineStudio.Technician.ViewModels
{
    /// <summary>
    /// Represents the MachineTechView View Model.
    /// </summary>
    /// <seealso cref="Tango.SharedUI.ViewModel" />
    /// <seealso cref="Tango.MachineStudio.Common.StudioApplication.IShutdownListener" />
    public class MachineTechViewVM : StudioViewModel
    {
        private class PackColor
        {
            public int Index { get; set; }
            public Color Color { get; set; }
            public String Name { get; set; }
        }

        private List<PropertyInfo> _diagnoticsMonitorsDataProperties;
        private IDiagnosticsFrameProvider _diagnosticsFrameProvider;
        private Dictionary<SingleGraphItem, TechGraphController> _singleControllers;
        private Dictionary<MultiGraphItem, TechGraphController> _multiControllers;
        private static object _elementsLock = new object();
        private String _lastTechProjectFile;
        private INotificationProvider _notification;
        private IEventLogger _eventLogger;
        private DateTime _lastDiagnosticsResponseUpdate;
        private const int MIN_DIAGNOSTICS_UPDATE_MILI = 500;
        private TechnicianModuleSettings _settings;

        private List<SingleTechRecordingData> _single_graphs_recordings;
        private List<MultiTechRecordingData> _multi_graph_recordings;
        private List<SingleTechRecordingData> _single_monitors_recordings;
        private List<MultiTechRecordingData> _multi_monitors_recordings;

        private DateTime _start_time = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, 0, 0, 0);
        private DateTime _last_time = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, 0, 0, 0);
        private Machine _machine;
        private List<PackColor> _packsColors;
        private int _diagnostics_index = 0;
        private DateTime _diagnosticsStartTime;
        private DateTime _diagnosticsNowTime;

        private ProducerConsumerQueue<StartDiagnosticsResponse> _framesQueue;
        private Thread _populateFramesThread;

        #region Properties

        private ObservableCollection<MachineTechTabVM> _tabs;
        /// <summary>
        /// Gets or sets the elements tabs.
        /// </summary>
        public ObservableCollection<MachineTechTabVM> Tabs
        {
            get { return _tabs; }
            set { _tabs = value; RaisePropertyChangedAuto(); }
        }

        private MachineTechTabVM _selectedTab;
        /// <summary>
        /// Gets or sets the selected tab.
        /// </summary>
        public MachineTechTabVM SelectedTab
        {
            get { return _selectedTab; }
            set
            {
                _selectedTab = value;
                RaisePropertyChangedAuto();

                foreach (var tab in Tabs.Where(x => x != _selectedTab))
                {
                    tab.IsSelected = false;
                }

                if (_selectedTab != null)
                {
                    _selectedTab.IsSelected = true;
                }

                EnableRenderingForSelectedTabGraphs();
            }
        }

        private ObservableCollection<TechItem> _availableTechItems;
        /// <summary>
        /// Gets or sets the available tech items.
        /// </summary>
        public ObservableCollection<TechItem> AvailableTechItems
        {
            get { return _availableTechItems; }
            set { _availableTechItems = value; RaisePropertyChangedAuto(); }
        }

        private TechItem _selectedTechItem;
        /// <summary>
        /// Gets or sets the selected available tech item.
        /// </summary>
        public TechItem SelectedTechItem
        {
            get { return _selectedTechItem; }
            set { _selectedTechItem = value; RaisePropertyChangedAuto(); }
        }

        /// <summary>
        /// Gets or sets the db adapter.
        /// </summary>
        public ObservablesStaticCollections Adapter { get; set; }

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

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

        private bool _disableRendering;
        /// <summary>
        /// Gets or sets a value indicating whether [disable rendering].
        /// </summary>
        public bool DisableRendering
        {
            get { return _disableRendering; }
            set { _disableRendering = value; RaisePropertyChangedAuto(); OnDisableRenderingChanged(); }
        }

        private bool _hideMenu;
        /// <summary>
        /// Gets or sets a value indicating whether [hide menu].
        /// </summary>
        public bool HideMenu
        {
            get { return _hideMenu; }
            set { _hideMenu = value; RaisePropertyChangedAuto(); }
        }

        private StartDiagnosticsResponse _currentDiagnosticsResponse;
        /// <summary>
        /// Gets or sets the current diagnostics response.
        /// </summary>
        public StartDiagnosticsResponse CurrentDiagnosticsResponse
        {
            get { return _currentDiagnosticsResponse; }
            set { _currentDiagnosticsResponse = value; RaisePropertyChanged(nameof(CurrentDiagnosticsResponse)); }
        }

        private int _currentDiagnosticsResponseSize;
        /// <summary>
        /// Gets or sets the size of the current diagnostics response.
        /// </summary>
        /// <value>
        /// The size of the current diagnostics response.
        /// </value>
        public int CurrentDiagnosticsResponseSize
        {
            get { return _currentDiagnosticsResponseSize; }
            set { _currentDiagnosticsResponseSize = value; RaisePropertyChanged(nameof(CurrentDiagnosticsResponseSize)); }
        }

        private int _graphsDurationSeconds;
        /// <summary>
        /// Gets or sets the graphs duration seconds.
        /// </summary>
        public int GraphsDurationSeconds
        {
            get { return _graphsDurationSeconds; }
            set { _graphsDurationSeconds = value; RaisePropertyChangedAuto(); }
        }

        private int _tempGraphsDurationSeconds;
        /// <summary>
        /// Gets or sets the temporary graphs duration seconds.
        /// </summary>
        public int TempGraphsDurationSeconds
        {
            get { return _tempGraphsDurationSeconds; }
            set { _tempGraphsDurationSeconds = value; RaisePropertyChangedAuto(); }
        }



        #endregion

        #region Commands

        /// <summary>
        /// Gets or sets the save as project command.
        /// </summary>
        public RelayCommand SaveAsProjectCommand { get; set; }

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

        /// <summary>
        /// Gets or sets the open project command.
        /// </summary>
        public RelayCommand OpenProjectCommand { get; set; }

        /// <summary>
        /// Gets or sets the synchronize hardware configuration command.
        /// </summary>
        public RelayCommand SyncHardwareConfigurationCommand { get; set; }

        /// <summary>
        /// Gets or sets the upload hardware configuration command.
        /// </summary>
        public RelayCommand UploadHardwareConfigurationCommand { get; set; }

        /// <summary>
        /// Gets or sets the reset hardware configuration command.
        /// </summary>
        public RelayCommand ResetHardwareConfigurationCommand { get; set; }

        /// <summary>
        /// Gets or sets the update graphs duration command.
        /// </summary>
        public RelayCommand UpdateGraphsDurationCommand { get; set; }

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

        /// <summary>
        /// Gets or sets the remove tab command.
        /// </summary>
        public RelayCommand<MachineTechTabVM> RemoveTabCommand { get; set; }

        /// <summary>
        /// Gets or sets the new project command.
        /// </summary>
        public RelayCommand NewProjectCommand { get; set; }

        /// <summary>
        /// Gets or sets the rename tab command.
        /// </summary>
        public RelayCommand RenameTabCommand { get; set; }

        /// <summary>
        /// Gets or sets the import project tabs command.
        /// </summary>
        /// <value>
        /// The import project tabs command.
        /// </value>
        public RelayCommand ImportProjectTabsCommand { get; set; }

        /// <summary>
        /// Gets or sets the upload partial hardware configuration command.
        /// </summary>
        public RelayCommand UploadPartialHardwareConfigurationCommand { get; set; }

        /// <summary>
        /// Gets or sets the reset command.
        /// </summary>
        public RelayCommand ResetGraphsCommand { get; set; }

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="MachineTechViewVM"/> class.
        /// </summary>
        /// <param name="applicationManager">The application manager.</param>
        /// <param name="notificationProvider">The notification provider.</param>
        public MachineTechViewVM(IStudioApplicationManager applicationManager, INotificationProvider notificationProvider, IDiagnosticsFrameProvider diagnosticsFrameProvider, IEventLogger eventLogger)
        {
            _framesQueue = new ProducerConsumerQueue<StartDiagnosticsResponse>();

            Tabs = new ObservableCollection<MachineTechTabVM>();
            Tabs.Add(new MachineTechTabVM() { IsSelected = true, Name = "Untitled" });
            SelectedTab = Tabs.First();

            _settings = SettingsManager.Default.GetOrCreate<TechnicianModuleSettings>();

            _single_graphs_recordings = new List<SingleTechRecordingData>();
            _multi_graph_recordings = new List<MultiTechRecordingData>();

            _single_monitors_recordings = new List<SingleTechRecordingData>();
            _multi_monitors_recordings = new List<MultiTechRecordingData>();

            GraphsDurationSeconds = _settings.GraphsDuration;
            TempGraphsDurationSeconds = GraphsDurationSeconds;

            _notification = notificationProvider;
            _eventLogger = eventLogger;
            _singleControllers = new Dictionary<SingleGraphItem, TechGraphController>();
            _multiControllers = new Dictionary<MultiGraphItem, TechGraphController>();
            AvailableTechItems = TechItem.GetAvailableTechItems().ToObservableCollection();
            SelectedTechItem = AvailableTechItems.FirstOrDefault();
            _diagnoticsMonitorsDataProperties = typeof(DiagnosticsMonitors).GetProperties(BindingFlags.Public | BindingFlags.Instance).ToList();
            ApplicationManager = applicationManager;
            ApplicationManager.ConnectedMachineChanged += ApplicationManager_ConnectedMachineChanged;

            Adapter = ObservablesStaticCollections.Instance;

            OpenProjectCommand = new RelayCommand(OpenProject);
            SaveAsProjectCommand = new RelayCommand(SaveAsProject);
            SaveProjectCommand = new RelayCommand(SaveProject);

            _lastTechProjectFile = _settings.LastTechProjectFile;

            if (File.Exists(_lastTechProjectFile))
            {
                try
                {
                    OpenProjectFile(_lastTechProjectFile);
                }
                catch (Exception ex)
                {
                    LogManager.Log(ex, "Error loading last project file.");
                }
            }

            _diagnosticsFrameProvider = diagnosticsFrameProvider;
            _diagnosticsFrameProvider.FrameReceived += DiagnosticsFrameProvider_FrameReceived;

            UploadHardwareConfigurationCommand = new RelayCommand(UploadHardwareConfiguration);
            SyncHardwareConfigurationCommand = new RelayCommand(SyncHardwareConfiguration);
            ResetHardwareConfigurationCommand = new RelayCommand(() => ResetHardwareConfiguration());
            UpdateGraphsDurationCommand = new RelayCommand(() =>
            {
                GraphsDurationSeconds = TempGraphsDurationSeconds;
                _settings.GraphsDuration = GraphsDurationSeconds;
                ClearAllGraphs();
            });

            AddTabCommand = new RelayCommand(() => AddNewTab());
            RemoveTabCommand = new RelayCommand<MachineTechTabVM>(RemoveTab);
            NewProjectCommand = new RelayCommand(CreateNewProject);
            RenameTabCommand = new RelayCommand(RenameTab);
            ImportProjectTabsCommand = new RelayCommand(ImportProjectTabs);
            UploadPartialHardwareConfigurationCommand = new RelayCommand(UploadPartialHardwareConfiguration);
            ResetGraphsCommand = new RelayCommand(ClearAllGraphs);
        }

        #endregion

        #region Event Handlers

        /// <summary>
        /// Applications the manager connected machine changed.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="machine">The machine.</param>
        private async void ApplicationManager_ConnectedMachineChanged(object sender, IExternalBridgeClient machine)
        {
            MachineOperator = machine;

            if (MachineOperator != null)
            {
                ResetHardwareConfiguration(false);
            }

            ClearAllGraphs();

            if (machine != null)
            {
                try
                {
                    using (ObservablesContext db = ObservablesContext.CreateDefault())
                    {
                        _machine = await new MachineBuilder(db).Set(machine.Machine.Guid).WithConfiguration().BuildAsync();

                        _packsColors = new List<PackColor>();

                        foreach (var pack in _machine.Configuration.NoneEmptyIdsPacks.OrderBy(x => x.PackIndex).ToList())
                        {
                            PackColor pc = new PackColor();
                            pc.Index = pack.PackIndex;
                            pc.Name = pack.LiquidType.Name;

                            if (pack.LiquidType.LiquidTypeColor == Colors.Black)
                            {
                                pc.Color = Colors.Gray;
                            }
                            else if (pack.LiquidType.LiquidTypeColor == Colors.Transparent)
                            {
                                pc.Color = Colors.White;
                            }
                            else
                            {
                                pc.Color = pack.LiquidType.LiquidTypeColor;
                            }

                            _packsColors.Add(pc);
                        }
                    }
                }
                catch (Exception ex)
                {
                    LogManager.Log(ex, LogCategory.Warning, "Error loading the connected machine details from db.");
                }
            }
        }

        /// <summary>
        /// Handles the diagnostics frame provider data.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="response">The response.</param>
        private void DiagnosticsFrameProvider_FrameReceived(object sender, StartDiagnosticsResponse response)
        {
            _framesQueue.BlockEnqueue(response);
        }

        #endregion

        #region Populate Diagnostics Data

        private void PopulateFramesThreadMethod()
        {
            while (!ApplicationManager.IsShuttingDown)
            {
                var frame = _framesQueue.BlockDequeue();
                PopulateDiagnosticsData(frame);
            }
        }

        /// <summary>
        /// Populates the diagnostics data to the proper elements.
        /// </summary>
        /// <param name="data">The data.</param>
        private void PopulateDiagnosticsData(StartDiagnosticsResponse data)
        {
            if (data.Monitors == null || _machineOperator == null || _machineOperator.DeviceInformation == null) return;

            _diagnostics_index++;
            uint interval = _machineOperator.DeviceInformation.DiagnosticsInterval;

            TimeSpan delta_base = DateTime.Now - _start_time;
            TimeSpan delta = (DateTime.Now - _last_time);
            _last_time = DateTime.Now;

            if (data.ElapsedMilli > 0)
            {
                _diagnosticsNowTime = DateTime.ParseExact(data.DateTime, "MM/dd/yyyy HH:mm:ss.fff", null); //_diagnosticsNowTime.Add(TimeSpan.FromMilliseconds(data.ElapsedMilli));
                //var now = _diagnosticsStartTime.Add(TimeSpan.FromMilliseconds(interval * _diagnostics_index));
                //Debug.WriteLine("DeltaBase Before: " + delta_base);
                delta_base = _diagnosticsNowTime - _start_time;
                //Debug.WriteLine("DeltaBase After: " + delta_base);

                //Debug.WriteLine("Delta Before: " + delta);
                delta = TimeSpan.FromMilliseconds(data.ElapsedMilli);
                //Debug.WriteLine("Delta After: " + delta);
            }

            double delta_mili = delta.TotalMilliseconds;

            if (DateTime.Now > _lastDiagnosticsResponseUpdate.AddMilliseconds(MIN_DIAGNOSTICS_UPDATE_MILI))
            {
                CurrentDiagnosticsResponse = data;
                _lastDiagnosticsResponseUpdate = DateTime.Now;
                CurrentDiagnosticsResponseSize = data.CalculateSize();
            }

            foreach (var sr in _single_monitors_recordings)
            {
                var techMonitor = (sr.Tag as TechMonitor);

                var prop = _diagnoticsMonitorsDataProperties.SingleOrDefault(x => x.Name == techMonitor.Name);

                if (prop != null)
                {
                    var points = GetDataArray(techMonitor, prop.GetValue(data.Monitors));

                    if (points.Count > 0)
                    {
                        sr.PushData(points, delta_base, delta);
                    }
                }
            }

            foreach (var mr in _multi_monitors_recordings)
            {
                var techMonitor = (mr.Tag as TechMonitor);

                var prop = _diagnoticsMonitorsDataProperties.SingleOrDefault(x => x.Name == techMonitor.Name);

                if (prop != null)
                {
                    var points = GetDataMatrix(techMonitor, prop.GetValue(data.Monitors));
                    if (points.Count > 0)
                    {
                        mr.PushData(points, delta_base, delta);
                    }
                }
            }

            lock (_elementsLock)
            {
                var elements = Tabs.SelectMany(x => x.Elements).ToList();
                var selectedTabElements = SelectedTab.Elements.ToList();

                //Can be updated only when visible
                foreach (var item in selectedTabElements.Select(x => x.HostedElement as TechItem))
                {
                    if (item.GetType() == typeof(MonitorItem))
                    {
                        MonitorItem monitorItem = item as MonitorItem;

                        if (DateTime.Now > monitorItem.LastUpdateTime.AddMilliseconds(monitorItem.UpdateInterval))
                        {
                            var prop = _diagnoticsMonitorsDataProperties.SingleOrDefault(x => x.Name == monitorItem.TechMonitor.Name);

                            if (prop != null)
                            {
                                monitorItem.Value = GetDataLastValue(monitorItem.TechMonitor, prop.GetValue(data.Monitors));
                            }
                        }
                    }
                    else if (item.GetType() == typeof(MeterItem))
                    {
                        MeterItem meterItem = item as MeterItem;

                        if (DateTime.Now > meterItem.LastUpdateTime.AddMilliseconds(meterItem.UpdateInterval))
                        {
                            var prop = _diagnoticsMonitorsDataProperties.SingleOrDefault(x => x.Name == meterItem.TechMonitor.Name);

                            if (prop != null)
                            {
                                meterItem.Value = GetDataLastValue(meterItem.TechMonitor, prop.GetValue(data.Monitors));
                            }
                        }
                    }
                    else if (item.GetType() == typeof(DigitalOutItem))
                    {
                        DigitalOutItem digitalOutItem = item as DigitalOutItem;

                        var digitalPin = data.DigitalInterfaceStates.SingleOrDefault(x => x.InterfaceIO == (InterfaceIOs)digitalOutItem.TechIo.Code);

                        if (digitalPin != null)
                        {
                            digitalOutItem.EffectiveValue = digitalPin.Value;
                        }
                    }
                    else if (item.GetType() == typeof(DigitalInItem))
                    {
                        DigitalInItem digitalInItem = item as DigitalInItem;

                        var digitalPin = data.DigitalInterfaceStates.SingleOrDefault(x => x.InterfaceIO == (InterfaceIOs)digitalInItem.TechIo.Code);

                        if (digitalPin != null)
                        {
                            digitalInItem.Value = digitalPin.Value;
                        }
                    }
                    else if (item.GetType() == typeof(HeaterItem))
                    {
                        HeaterItem heaterItem = item as HeaterItem;

                        var heaterState = data.HeatersStates.SingleOrDefault(x => x.HeaterType == (HeaterType)heaterItem.TechHeater.Code);

                        if (heaterState != null)
                        {
                            heaterItem.HeaterState = heaterState;
                        }
                    }
                    else if (item.GetType() == typeof(ValveItem))
                    {
                        ValveItem valveItem = item as ValveItem;

                        var valveState = data.ValvesStates.SingleOrDefault(x => x.ValveType == (ValveType)valveItem.TechValve.Code);

                        if (valveState != null)
                        {
                            valveItem.EffectiveState = valveState.State;
                        }
                    }
                    else if (item.GetType() == typeof(BlowerItem))
                    {
                        BlowerItem blowerItem = item as BlowerItem;

                        if (data.Monitors.BlowerVoltage.Count > 0)
                        {
                            blowerItem.EffectiveActive = data.Monitors.BlowerVoltage.Last() > 0;
                        }
                    }
                    else if (item.GetType() == typeof(ControllerItem))
                    {
                        ControllerItem controllerItem = item as ControllerItem;

                        if (DateTime.Now > controllerItem.LastUpdateTime.AddMilliseconds(controllerItem.UpdateInterval))
                        {
                            var componentState = data.ComponentsStates.SingleOrDefault(x => (int)x.Component == controllerItem.TechController.Code);

                            if (componentState != null)
                            {
                                controllerItem.EffectiveValue = componentState.Value;
                            }
                        }
                    }
                }

                //Must be updated all the time.
                foreach (var item in elements.Select(x => x.HostedElement as TechItem))
                {
                    if (item.GetType() == typeof(SingleGraphItem))
                    {
                        SingleGraphItem graphItem = item as SingleGraphItem;

                        var prop = _diagnoticsMonitorsDataProperties.SingleOrDefault(x => x.Name == graphItem.TechMonitor.Name);

                        if (prop != null)
                        {
                            TechGraphController controller = null;

                            if (_singleControllers.TryGetValue(graphItem, out controller))
                            {
                                var points = GetDataArray(graphItem.TechMonitor, prop.GetValue(data.Monitors));

                                if (points.Count > 0)
                                {
                                    List<TimeSpanDataPoint> times = new List<TimeSpanDataPoint>();
                                    var dPoints = points.Select(x => new DoubleDataPoint(x)).ToList();

                                    for (int i = 0; i < points.Count; i++)
                                    {
                                        times.Add(delta_base.Add(TimeSpan.FromMilliseconds((delta_mili / points.Count) * i)));
                                    }

                                    controller.PushData(times, dPoints);

                                    var _graph_recording = _single_graphs_recordings.SingleOrDefault(x => x.Tag == graphItem);
                                    if (_graph_recording != null)
                                    {
                                        _graph_recording.PushData(points, delta_base, delta);
                                    }
                                }
                            }
                        }
                    }
                    else if (item.GetType() == typeof(MultiGraphItem))
                    {
                        MultiGraphItem graphItem = item as MultiGraphItem;

                        var prop = _diagnoticsMonitorsDataProperties.SingleOrDefault(x => x.Name == graphItem.TechMonitor.Name);

                        if (prop != null)
                        {
                            TechGraphController controller = null;

                            if (_multiControllers.TryGetValue(graphItem, out controller))
                            {
                                var points = GetDataMatrix(graphItem.TechMonitor, prop.GetValue(data.Monitors));

                                if (points.Count > 0)
                                {
                                    List<TimeSpanDataPoint> times = new List<TimeSpanDataPoint>();
                                    var dPoints = points.Select(x => new List<DoubleDataPoint>(x.Select(y => new DoubleDataPoint(y)))).ToList();

                                    for (int i = 0; i < points[0].Count; i++)
                                    {
                                        times.Add(delta_base.Add(TimeSpan.FromMilliseconds((delta_mili / points[0].Count) * i)));
                                    }

                                    List<List<TimeSpanDataPoint>> timesMat = new List<List<TimeSpanDataPoint>>();

                                    for (int i = 0; i < controller.DataSeriesCollection.Count; i++)
                                    {
                                        timesMat.Add(times);
                                    }

                                    try
                                    {
                                        if (_packsColors != null)
                                        {
                                            foreach (var pack in _packsColors)
                                            {
                                                controller.DataSeriesCollection[pack.Index].Stroke = pack.Color;
                                                controller.DataSeriesCollection[pack.Index].Name = pack.Name;
                                            }
                                        }
                                    }
                                    catch (Exception ex)
                                    {
                                        Debug.WriteLine($"Error assigning multi graph colors.\n{ex.ToString()}");
                                    }

                                    controller.PushData(timesMat, dPoints);
                                }

                                var _graph_recording = _multi_graph_recordings.SingleOrDefault(x => x.Tag == graphItem);
                                if (_graph_recording != null)
                                {
                                    _graph_recording.PushData(points, delta_base, delta);
                                }
                            }
                        }
                    }
                }
            }
        }

        #endregion

        #region Private Methods

        private void EnableRenderingForSelectedTabGraphs()
        {
            if (SelectedTab != null)
            {
                lock (_elementsLock)
                {
                    var elements = Tabs.SelectMany(x => x.Elements).ToList();

                    foreach (var element in elements)
                    {
                        if (SelectedTab.Elements.Contains(element))
                        {
                            var item = element.HostedElement as TechItem;

                            if (item != null)
                            {
                                if (item.GetType() == typeof(SingleGraphItem))
                                {
                                    SingleGraphItem graphItem = item as SingleGraphItem;

                                    TechGraphController controller = null;

                                    if (_singleControllers.TryGetValue(graphItem, out controller))
                                    {
                                        controller.DisableRendering = false;
                                    }
                                }
                                else if (item.GetType() == typeof(MultiGraphItem))
                                {
                                    MultiGraphItem graphItem = item as MultiGraphItem;

                                    TechGraphController controller = null;

                                    if (_multiControllers.TryGetValue(graphItem, out controller))
                                    {
                                        controller.DisableRendering = false;
                                    }
                                }
                            }
                        }
                        else
                        {
                            var item = element.HostedElement as TechItem;

                            if (item != null)
                            {
                                if (item.GetType() == typeof(SingleGraphItem))
                                {
                                    SingleGraphItem graphItem = item as SingleGraphItem;

                                    TechGraphController controller = null;

                                    if (_singleControllers.TryGetValue(graphItem, out controller))
                                    {
                                        controller.DisableRendering = true;
                                    }
                                }
                                else if (item.GetType() == typeof(MultiGraphItem))
                                {
                                    MultiGraphItem graphItem = item as MultiGraphItem;

                                    TechGraphController controller = null;

                                    if (_multiControllers.TryGetValue(graphItem, out controller))
                                    {
                                        controller.DisableRendering = true;
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Gets the last data point from a protobuf repeated field.
        /// </summary>
        /// <param name="monitor">The monitor.</param>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        private double GetDataLastValue(TechMonitor monitor, object value)
        {
            if (!monitor.MultiChannel)
            {
                RepeatedField<double> arr = value as RepeatedField<double>;
                return arr.LastOrDefault();
            }
            else
            {
                RepeatedField<DoubleArray> arr = value as RepeatedField<DoubleArray>;
                return arr.Last().Data.Last();
            }
        }

        /// <summary>
        /// Gets the data array from a protobuf repeated field.
        /// </summary>
        /// <param name="monitor">The monitor.</param>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        private List<double> GetDataArray(TechMonitor monitor, object value)
        {
            return (value as RepeatedField<double>).ToList();
        }

        /// <summary>
        /// Gets the data matrix from a protobuf repeated field of <see cref="DoubleArray"/>.
        /// </summary>
        /// <param name="monitor">The monitor.</param>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        private List<List<double>> GetDataMatrix(TechMonitor monitor, object value)
        {
            DoubleArray[] arrayOfDoubles = Enumerable.ToArray(value as IEnumerable<DoubleArray>);
            return arrayOfDoubles.Select(x => x.Data.ToList()).ToList();
        }

        #endregion

        #region Virtual Methods

        /// <summary>
        /// Called when the disable rendering has been changed
        /// </summary>
        protected virtual void OnDisableRenderingChanged()
        {
            //foreach (var controller in _singleControllers)
            //{
            //    controller.Value.ChangeRenderMode(!DisableRendering);
            //}

            //foreach (var controller in _multiControllers)
            //{
            //    controller.Value.ChangeRenderMode(!DisableRendering);
            //}
        }

        #endregion

        #region Add/Remove Element

        /// <summary>
        /// Creates a new tech element by the specified bounds and the current selected element.
        /// </summary>
        /// <param name="bounds">The bounds.</param>
        public void CreateElement(Rect bounds)
        {
            CreateElement(SelectedTechItem, bounds);
        }

        /// <summary>
        /// Creates a new tech element by the specified tech item instance and bounds.
        /// </summary>
        /// <param name="item">The item.</param>
        /// <param name="bounds">The bounds.</param>
        private void CreateElement(TechItem item, Rect bounds)
        {
            if (item is MonitorItem)
            {
                CreateElement<MonitorElementEditor, MonitorItem, TechMonitor>(bounds, Adapter.TechMonitors.Where(x => !x.MultiChannel).FirstOrDefault());
            }
            else if (item is MeterItem)
            {
                CreateElement<MeterElementEditor, MeterItem, TechMonitor>(bounds, Adapter.TechMonitors.Where(x => !x.MultiChannel).FirstOrDefault());
            }
            else if (item is HeaterItem)
            {
                var editor = CreateElement<HeaterElementEditor, HeaterItem, TechHeater>(bounds, Adapter.TechHeaters.FirstOrDefault());
                InitTechHeater(editor.HeaterItem);
            }
            else if (item is ValveItem)
            {
                var editor = CreateElement<ValveElementEditor, ValveItem, TechValve>(bounds, Adapter.TechValves.FirstOrDefault());
                InitTechValveItem(editor.ValveItem);
            }
            else if (item is SingleGraphItem)
            {
                var editor = CreateElement<SingleGraphElementEditor, SingleGraphItem, TechMonitor>(bounds, Adapter.TechMonitors.Where(x => !x.MultiChannel).FirstOrDefault());
                InitSingleGraphitem(editor.GraphItem, editor);
            }
            else if (item is MultiGraphItem)
            {
                var editor = CreateElement<MultiGraphElementEditor, MultiGraphItem, TechMonitor>(bounds, Adapter.TechMonitors.Where(x => x.MultiChannel).FirstOrDefault());
                InitMultiGraphItem(editor.GraphItem, editor);
            }
            else if (item is MotorItem)
            {
                var editor = CreateElement<MotorElementEditor, MotorItem, HardwareMotorType>(bounds, Adapter.HardwareMotorTypes.FirstOrDefault());
                InitMotorItem(editor.MotorItem);
            }
            else if (item is DispenserItem)
            {
                var editor = CreateElement<DispenserElementEditor, DispenserItem, TechDispenser>(bounds, Adapter.TechDispensers.FirstOrDefault());
                InitDispenserItem(editor.DispenserItem);
            }
            else if (item is ThreadMotionItem)
            {
                var editor = CreateElement<ThreadMotionElementEditor, ThreadMotionItem, object>(bounds, null);
                InitThreadMotionItem(editor.ThreadMotionItem);
            }
            else if (item is MotorGroupItem)
            {
                var editor = CreateElement<MotorGroupElementEditor, MotorGroupItem, object>(bounds, null);
                InitMotorGroupItem(editor.MotorGroupItem);
            }
            else if (item is DigitalOutItem)
            {
                var editor = CreateElement<DigitalOutElementEditor, DigitalOutItem, TechIo>(bounds, Adapter.TechIos.Where(x => x.Type == IOType.DigitalOutput.ToInt32()).FirstOrDefault());
                InitDigitalOutItem(editor.DigitalOutItem);
            }
            else if (item is DigitalInItem)
            {
                CreateElement<DigitalInElementEditor, DigitalInItem, TechIo>(bounds, Adapter.TechIos.Where(x => x.Type == IOType.DigitalInput.ToInt32()).FirstOrDefault());
            }
            else if (item is ControllerItem)
            {
                var editor = CreateElement<ControllerElementEditor, ControllerItem, TechController>(bounds, Adapter.TechControllers.FirstOrDefault());
                InitControllerItem(editor.ControllerItem);
            }
            else if (item is PidItem)
            {
                CreateElement<PidElementEditor, PidItem, HardwarePidControlType>(bounds, Adapter.HardwarePidControlTypes.FirstOrDefault());
            }
            else if (item is WinderItem)
            {
                CreateElement<WinderElementEditor, WinderItem, HardwareWinderType>(bounds, Adapter.HardwareWinderTypes.FirstOrDefault());
            }
            else if (item is DancerItem)
            {
                CreateElement<DancerElementEditor, DancerItem, HardwareDancerType>(bounds, Adapter.HardwareDancerTypes.FirstOrDefault());
            }
            else if (item is SpeedSensorItem)
            {
                CreateElement<SpeedSensorElementEditor, SpeedSensorItem, HardwareSpeedSensorType>(bounds, Adapter.HardwareSpeedSensorTypes.FirstOrDefault());
            }
            else if (item is BlowerItem)
            {
                var editor = CreateElement<BlowerElementEditor, BlowerItem, HardwareBlowerType>(bounds, Adapter.HardwareBlowerTypes.FirstOrDefault());
                InitBlowerItem(editor.BlowerItem);
            }
            else if (item is BreakSensorItem)
            {
                CreateElement<BreakSensorElementEditor, BreakSensorItem, HardwareBreakSensorType>(bounds, Adapter.HardwareBreakSensorTypes.FirstOrDefault());
            }
            else if (item is ProcessParametersItem)
            {
                var editor = CreateElement<ProcessParametersElementEditor, ProcessParametersItem, object>(bounds, null);
                InitProcessParameterItem(editor.ProcessParametersItem);
            }
            else if (item is JobRunnerItem)
            {
                var editor = CreateElement<JobRunnerElementEditor, JobRunnerItem, object>(bounds, null);
                InitJobRunnerItem(editor.JobRunnerItem);
            }
            else if (item is TextItem)
            {
                CreateElement<TextElementEditor, TextItem, object>(bounds, null);
            }
            else if (item is MonitorRecorderItem)
            {
                var editor = CreateElement<MonitorRecorderElementEditor, MonitorRecorderItem, object>(bounds, null);
                InitMonitorRecorderItem(editor.MonitorRecorderItem);
            }
        }

        /// <summary>
        /// Creates a new element by the specified editor type, tech item type, bounds and tech item constructor value.
        /// </summary>
        /// <typeparam name="Editor">The type of the editor.</typeparam>
        /// <typeparam name="Tech">The type of the tech.</typeparam>
        /// <typeparam name="Value">The type of the value.</typeparam>
        /// <param name="bounds">The bounds.</param>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        private Editor CreateElement<Editor, Tech, Value>(Rect bounds, Value value) where Editor : IElementEditor where Tech : TechItem
        {
            TechItem item = Activator.CreateInstance(typeof(Tech), new object[] { value }) as TechItem;
            IElementEditor editor = Activator.CreateInstance(typeof(Editor), new object[] { ((Tech)item), bounds }) as IElementEditor;
            SelectedTab.Elements.Add(editor);
            return (Editor)editor;
        }

        /// <summary>
        /// Creates a new element by the specified editor type and tech item instance.
        /// </summary>
        /// <typeparam name="Editor">The type of the editor.</typeparam>
        /// <param name="item">The item.</param>
        /// <returns></returns>
        private Editor CreateElement<Editor>(TechItem item) where Editor : IElementEditor
        {
            IElementEditor editor = Activator.CreateInstance(typeof(Editor), new object[] { item, item.GetBounds() }) as IElementEditor;
            SelectedTab.Elements.Add(editor);
            return (Editor)editor;
        }

        /// <summary>
        /// Adds a new tech item.
        /// </summary>
        /// <param name="item">The item.</param>
        private void AddTechItem(TechItem item)
        {
            if (item is MonitorItem)
            {
                (item as MonitorItem).TechMonitor = Adapter.TechMonitors.Where(x => !x.MultiChannel).FirstOrDefault(x => x.Guid == item.ItemGuid);
                CreateElement<MonitorElementEditor>(item);
            }
            else if (item is HeaterItem)
            {
                (item as HeaterItem).TechHeater = Adapter.TechHeaters.FirstOrDefault(x => x.Guid == item.ItemGuid);
                CreateElement<HeaterElementEditor>(item);
            }
            else if (item is ValveItem)
            {
                (item as ValveItem).TechValve = Adapter.TechValves.FirstOrDefault(x => x.Guid == item.ItemGuid);
                var editor = CreateElement<ValveElementEditor>(item);
                InitTechValveItem(editor.ValveItem);
            }
            else if (item is MeterItem)
            {
                (item as MeterItem).TechMonitor = Adapter.TechMonitors.Where(x => !x.MultiChannel).FirstOrDefault(x => x.Guid == item.ItemGuid);
                CreateElement<MeterElementEditor>(item);
            }
            else if (item is SingleGraphItem)
            {
                (item as SingleGraphItem).TechMonitor = Adapter.TechMonitors.Where(x => !x.MultiChannel).FirstOrDefault(x => x.Guid == item.ItemGuid);
                var editor = CreateElement<SingleGraphElementEditor>(item);
                InitSingleGraphitem(editor.GraphItem, editor);
            }
            else if (item is MultiGraphItem)
            {
                (item as MultiGraphItem).TechMonitor = Adapter.TechMonitors.Where(x => x.MultiChannel).FirstOrDefault(x => x.Guid == item.ItemGuid);
                var editor = CreateElement<MultiGraphElementEditor>(item);
                InitMultiGraphItem(editor.GraphItem, editor);
            }
            else if (item is MotorItem)
            {
                (item as MotorItem).HardwareMotorType = Adapter.HardwareMotorTypes.FirstOrDefault(x => x.Guid == item.ItemGuid);
                var editor = CreateElement<MotorElementEditor>(item);
                InitMotorItem(editor.MotorItem);
            }
            else if (item is DispenserItem)
            {
                (item as DispenserItem).TechDispenser = Adapter.TechDispensers.FirstOrDefault(x => x.Guid == item.ItemGuid);
                var editor = CreateElement<DispenserElementEditor>(item);
                InitDispenserItem(editor.DispenserItem);
            }
            else if (item is ThreadMotionItem)
            {
                var editor = CreateElement<ThreadMotionElementEditor>(item);
                InitThreadMotionItem(editor.ThreadMotionItem);
            }
            else if (item is MotorGroupItem)
            {
                var editor = CreateElement<MotorGroupElementEditor>(item);
                InitMotorGroupItem(editor.MotorGroupItem);
            }
            else if (item is DigitalOutItem)
            {
                (item as DigitalOutItem).TechIo = Adapter.TechIos.FirstOrDefault(x => x.Guid == item.ItemGuid);
                var editor = CreateElement<DigitalOutElementEditor>(item);
                InitDigitalOutItem(editor.DigitalOutItem);
            }
            else if (item is DigitalInItem)
            {
                (item as DigitalInItem).TechIo = Adapter.TechIos.FirstOrDefault(x => x.Guid == item.ItemGuid);
                CreateElement<DigitalInElementEditor>(item);
            }
            else if (item is ControllerItem)
            {
                (item as ControllerItem).TechController = Adapter.TechControllers.FirstOrDefault(x => x.Guid == item.ItemGuid);
                var editor = CreateElement<ControllerElementEditor>(item);
                InitControllerItem(editor.ControllerItem);
            }
            else if (item is PidItem)
            {
                (item as PidItem).HardwarePidType = Adapter.HardwarePidControlTypes.FirstOrDefault(x => x.Guid == item.ItemGuid);
                CreateElement<PidElementEditor>(item);
            }
            else if (item is WinderItem)
            {
                (item as WinderItem).HardwareWinderType = Adapter.HardwareWinderTypes.FirstOrDefault(x => x.Guid == item.ItemGuid);
                CreateElement<WinderElementEditor>(item);
            }
            else if (item is DancerItem)
            {
                (item as DancerItem).HardwareDancerType = Adapter.HardwareDancerTypes.FirstOrDefault(x => x.Guid == item.ItemGuid);
                CreateElement<DancerElementEditor>(item);
            }
            else if (item is SpeedSensorItem)
            {
                (item as SpeedSensorItem).HardwareSpeedSensorType = Adapter.HardwareSpeedSensorTypes.FirstOrDefault(x => x.Guid == item.ItemGuid);
                CreateElement<SpeedSensorElementEditor>(item);
            }
            else if (item is BlowerItem)
            {
                (item as BlowerItem).HardwareBlowerType = Adapter.HardwareBlowerTypes.FirstOrDefault(x => x.Guid == item.ItemGuid);
                var editor = CreateElement<BlowerElementEditor>(item);
                InitBlowerItem(editor.BlowerItem);
            }
            else if (item is BreakSensorItem)
            {
                (item as BreakSensorItem).HardwareBreakSensorType = Adapter.HardwareBreakSensorTypes.FirstOrDefault(x => x.Guid == item.ItemGuid);
                CreateElement<BreakSensorElementEditor>(item);
            }
            else if (item is ProcessParametersItem)
            {
                var editor = CreateElement<ProcessParametersElementEditor>(item);
                InitProcessParameterItem(editor.ProcessParametersItem);
            }
            else if (item is JobRunnerItem)
            {
                var editor = CreateElement<JobRunnerElementEditor>(item);
                InitJobRunnerItem(editor.JobRunnerItem);
            }
            else if (item is TextItem)
            {
                CreateElement<TextElementEditor>(item);
            }
            else if (item is MonitorRecorderItem)
            {
                var editor = CreateElement<MonitorRecorderElementEditor>(item);
                InitMonitorRecorderItem(editor.MonitorRecorderItem);
            }
        }

        /// <summary>
        /// Called when elements have been removed
        /// </summary>
        /// <param name="elements">The elements.</param>
        public void OnElementsRemoved(List<IElementEditor> elements)
        {
            foreach (var element in elements)
            {
                if (element.HostedElement is SingleGraphItem)
                {
                    var _graph_recording = _single_graphs_recordings.SingleOrDefault(x => x.Tag == element.HostedElement);

                    if (_graph_recording != null)
                    {
                        (_graph_recording.Tag as SingleGraphItem).StopRecording();
                        _single_graphs_recordings.Remove(_graph_recording);
                        _graph_recording.Dispose();
                    }
                }
                else if (element.HostedElement is MultiGraphItem)
                {
                    var _graph_recording = _multi_graph_recordings.SingleOrDefault(x => x.Tag == element.HostedElement);

                    if (_graph_recording != null)
                    {
                        (_graph_recording.Tag as MultiGraphItem).StopRecording();
                        _multi_graph_recordings.Remove(_graph_recording);
                        _graph_recording.Dispose();
                    }
                }
            }
        }

        /// <summary>
        /// Called when elements have been pasted
        /// </summary>
        /// <param name="elements">The elements.</param>
        public void OnElementsPasted(List<IElementEditor> elements)
        {
            foreach (var element in elements)
            {
                if (element is SingleGraphElementEditor)
                {
                    var graphItem = element.HostedElement as SingleGraphItem;
                    var editor = element as SingleGraphElementEditor;

                    TechGraphController controller = new TechGraphController();
                    controller.DataSeriesCollection.Add(new WpfGraphDataSeries()
                    {
                        Stroke = Colors.DodgerBlue,
                    });
                    editor.InnerGraph.Controller = controller;

                    graphItem.Editor = editor;

                    _singleControllers.Add(graphItem, controller);
                }
                else if (element is MultiGraphElementEditor)
                {
                    var graphItem = element.HostedElement as MultiGraphItem;
                    var editor = element as MultiGraphElementEditor;

                    TechGraphController controller = new TechGraphController(500);

                    for (int i = 0; i < graphItem.TechMonitor.ChannelCount; i++)
                    {
                        controller.DataSeriesCollection.Add(new WpfGraphDataSeries()
                        {
                            Stroke = ColorHelper.GetRandomColor(),
                            Name = graphItem.TechMonitor.Name.First() + (i + 1).ToString(),
                        });
                    }

                    editor.InnerGraph.Controller = controller;

                    graphItem.Editor = editor;

                    _multiControllers.Add(graphItem, controller);
                }
                else if (element is MotorElementEditor)
                {
                    var motorItem = element.HostedElement as MotorItem;
                    InitMotorItem(motorItem);
                }
                else if (element is DispenserElementEditor)
                {
                    var dispenser = element.HostedElement as DispenserItem;
                    InitDispenserItem(dispenser);
                }
                else if (element is DigitalOutElementEditor)
                {
                    var ioItem = element.HostedElement as DigitalOutItem;
                    InitDigitalOutItem(ioItem);
                }
                else if (element is ThreadMotionElementEditor)
                {
                    var threadMotionItem = element.HostedElement as ThreadMotionItem;
                    InitThreadMotionItem(threadMotionItem);
                }
                else if (element is MotorGroupElementEditor)
                {
                    var motorGroupItem = element.HostedElement as MotorGroupItem;
                    InitMotorGroupItem(motorGroupItem);
                }
                else if (element is ControllerElementEditor)
                {
                    var controllerItem = element.HostedElement as ControllerItem;
                    InitControllerItem(controllerItem);
                }
                else if (element is MonitorRecorderElementEditor)
                {
                    var item = element.HostedElement as MonitorRecorderItem;
                    InitMonitorRecorderItem(item);
                }
                else if (element is ValveElementEditor)
                {
                    var item = element.HostedElement as ValveItem;
                    InitTechValveItem(item);
                }
                else if (element is BlowerElementEditor)
                {
                    var item = element.HostedElement as BlowerItem;
                    InitBlowerItem(item);
                }
                else if (element is HeaterElementEditor)
                {
                    var item = element.HostedElement as HeaterItem;
                    InitTechHeater(item);
                }
                else if (element is ProcessParametersElementEditor)
                {
                    var item = element.HostedElement as ProcessParametersItem;
                    InitProcessParameterItem(item);
                }
            }
        }

        #endregion

        #region Init Tech Items

        /// <summary>
        /// Initializes the monitor recorder item.
        /// </summary>
        /// <param name="item">The item.</param>
        private void InitMonitorRecorderItem(MonitorRecorderItem item)
        {
            item.RecordingStarted += () =>
            {
                CommonOpenFileDialog dlg = new CommonOpenFileDialog();
                dlg.Title = "Select a folder to place all CSV files.";
                dlg.IsFolderPicker = true;
                if (dlg.ShowDialog() == CommonFileDialogResult.Ok)
                {
                    foreach (var monitor in item.GetSelectedMonitors())
                    {
                        if (!monitor.MultiChannel)
                        {
                            _single_monitors_recordings.Add(new SingleTechRecordingData(monitor.Description, dlg.FileName + "\\" + monitor.Description + ".csv") { Tag = monitor });
                        }
                        else
                        {
                            _multi_monitors_recordings.Add(new MultiTechRecordingData(monitor.Description, monitor.ChannelCount, dlg.FileName + "\\" + monitor.Description + ".csv") { Tag = monitor });
                        }
                        item.StartRecording();
                    }
                }
            };

            item.RecordingStopped += () =>
            {
                item.StopRecording();

                foreach (var sr in _single_monitors_recordings)
                {
                    sr.Dispose();
                }

                _single_monitors_recordings.Clear();

                foreach (var mr in _multi_monitors_recordings)
                {
                    mr.Dispose();
                }

                _multi_monitors_recordings.Clear();
            };
        }

        /// <summary>
        /// Initializes the tech valve.
        /// </summary>
        /// <param name="item">The valve item.</param>
        private void InitTechValveItem(ValveItem item)
        {
            item.StateChanged += async (x, state) =>
            {
                try
                {
                    CheckMachineOperator();
                    await MachineOperator.SetValveState((ValveType)item.TechValve.Code, state);
                }
                catch (Exception ex)
                {
                    LogManager.Log(ex, String.Format("Error executing technician set valve state command on '{0}'.", item.TechName));
                }
            };
        }

        /// <summary>
        /// Initializes the blower item.
        /// </summary>
        /// <param name="item">The blower item.</param>
        private void InitBlowerItem(BlowerItem item)
        {
            item.SetCommandClicked += async (_, isActive) =>
            {
                try
                {
                    CheckMachineOperator();
                    await MachineOperator.SetBlowerState((PMR.Hardware.HardwareBlowerType)item.HardwareBlower.HardwareBlowerType.Code, isActive, item.HardwareBlower.Voltage);
                }
                catch (Exception ex)
                {
                    LogManager.Log(ex, $"Error executing SetBlowerState command for blower {item.HardwareBlower.HardwareBlowerType.Name}.");
                }
            };
        }

        /// <summary>
        /// Initializes the tech heater.
        /// </summary>
        /// <param name="item">The item.</param>
        private void InitTechHeater(HeaterItem item)
        {
            item.SetCommandClicked += async () =>
            {
                try
                {
                    CheckMachineOperator();
                    await MachineOperator.SetHeaterState((HeaterType)item.TechHeater.Code, item.SetPoint);
                }
                catch (Exception ex)
                {
                    LogManager.Log(ex, $"Error executing SetHeaterState command for heater {item.TechHeater.Name}.");
                }
            };
        }

        /// <summary>
        /// Initializes the motor item.
        /// </summary>
        /// <param name="item">The item.</param>
        private void InitMotorItem(MotorItem item)
        {
            item.ActionExecuted += async (x, action) =>
            {
                try
                {
                    CheckMachineOperator();

                    if (action == MotorActionType.ForwardPressed)
                    {
                        await MachineOperator.StartMotorJogging(new MotorJoggingRequest()
                        {
                            MotorType = (PMR.Hardware.HardwareMotorType)item.HardwareMotorType.Code,
                            Speed = item.Speed,
                            Direction = MotorDirection.Forward,
                        });
                    }
                    else if (action == MotorActionType.ForwardReleased)
                    {
                        await MachineOperator.StopMotorJogging(new MotorAbortJoggingRequest()
                        {
                            MotorType = (PMR.Hardware.HardwareMotorType)item.HardwareMotorType.Code,
                        });
                    }
                    else if (action == MotorActionType.BackwardPressed)
                    {
                        await MachineOperator.StartMotorJogging(new MotorJoggingRequest()
                        {
                            MotorType = (PMR.Hardware.HardwareMotorType)item.HardwareMotorType.Code,
                            Speed = item.Speed,
                            Direction = MotorDirection.Backward,
                        });
                    }
                    else if (action == MotorActionType.BackwardReleased)
                    {
                        await MachineOperator.StopMotorJogging(new MotorAbortJoggingRequest()
                        {
                            MotorType = (PMR.Hardware.HardwareMotorType)item.HardwareMotorType.Code,
                        });
                    }
                    else if (action == MotorActionType.HomingForwardStarted || action == MotorActionType.HomingBackwardStarted)
                    {
                        item.HomingProgress = 0;
                        item.IsHoming = true;
                        item.IsHomingCompleted = false;

                        MachineOperator.StartMotorHoming(new MotorHomingRequest()
                        {
                            MotorType = (PMR.Hardware.HardwareMotorType)item.HardwareMotorType.Code,
                            Speed = item.Speed,
                            Direction = action == MotorActionType.HomingBackwardStarted ? MotorDirection.Backward : MotorDirection.Forward
                        })
                        .Subscribe((response) =>
                        {
                            item.HomingMaximumProgress = response.MaxProgress;
                            item.HomingProgress = response.Progress;

                        }, (ex) =>
                         {

                             item.IsHoming = false;
                             item.IsHomingCompleted = true;

                         }, () =>
                         {

                             item.IsHoming = false;
                             item.IsHomingCompleted = true;

                         });
                    }
                    else if (action == MotorActionType.HomingStopped)
                    {
                        await MachineOperator.StopMotorHoming(new MotorAbortHomingRequest()
                        {
                            MotorType = (PMR.Hardware.HardwareMotorType)item.HardwareMotorType.Code,
                        });

                        item.IsHoming = false;
                    }
                }
                catch (Exception ex)
                {
                    item.IsHomingCompleted = true;
                    item.IsHoming = false;
                    item.HomingProgress = 0;
                    item.HomingMaximumProgress = 0;
                    LogManager.Log(ex, String.Format("Error executing technician command '{0}' on item '{1}'.", action, item.TechName));
                }
            };
        }

        /// <summary>
        /// Initializes the dispenser item.
        /// </summary>
        /// <param name="item">The item.</param>
        private void InitDispenserItem(DispenserItem item)
        {
            item.ActionExecuted += async (x, action) =>
            {
                try
                {
                    CheckMachineOperator();

                    if (action == MotorActionType.ForwardPressed)
                    {
                        await MachineOperator.StartDispenserJogging(new DispenserJoggingRequest()
                        {
                            Index = item.TechDispenser.Code,
                            Direction = MotorDirection.Forward,
                            Speed = item.Speed,
                        });
                    }
                    else if (action == MotorActionType.ForwardReleased)
                    {
                        await MachineOperator.StopDispenserJogging(new DispenserAbortJoggingRequest()
                        {
                            Index = item.TechDispenser.Code,
                        });
                    }
                    else if (action == MotorActionType.BackwardPressed)
                    {
                        await MachineOperator.StartDispenserJogging(new DispenserJoggingRequest()
                        {
                            Index = item.TechDispenser.Code,
                            Direction = MotorDirection.Backward,
                            Speed = item.Speed,
                        });
                    }
                    else if (action == MotorActionType.BackwardReleased)
                    {
                        await MachineOperator.StopDispenserJogging(new DispenserAbortJoggingRequest()
                        {
                            Index = item.TechDispenser.Code,
                        });
                    }
                    else if (action == MotorActionType.HomingForwardStarted || action == MotorActionType.HomingBackwardStarted)
                    {
                        item.HomingProgress = 0;
                        item.IsHoming = true;
                        item.IsHomingCompleted = false;

                        MachineOperator.StartDispenserHoming(new DispenserHomingRequest()
                        {
                            Index = item.TechDispenser.Code,
                            Speed = item.Speed,
                            Direction = action == MotorActionType.HomingBackwardStarted ? MotorDirection.Backward : MotorDirection.Forward
                        })
                        .Subscribe((response) =>
                        {

                            item.HomingMaximumProgress = response.MaxProgress;
                            item.HomingProgress = response.Progress;

                        }, (ex) =>
                        {

                            item.IsHoming = false;
                            item.IsHomingCompleted = true;

                        }, () =>
                        {

                            item.IsHoming = false;
                            item.IsHomingCompleted = true;

                        });
                    }
                    else if (action == MotorActionType.HomingStopped)
                    {
                        await MachineOperator.StopDispenserHoming(new DispenserAbortHomingRequest()
                        {
                            Index = item.TechDispenser.Code,
                        });

                        item.IsHoming = false;
                    }
                }
                catch (Exception ex)
                {
                    item.IsHomingCompleted = true;
                    item.IsHoming = false;
                    item.HomingProgress = 0;
                    item.HomingMaximumProgress = 0;
                    LogManager.Log(ex, String.Format("Error executing technician command '{0}' on item '{1}'.", action, item.TechName));
                }
            };
        }

        /// <summary>
        /// Initializes the single graph item.
        /// </summary>
        /// <param name="item">The item.</param>
        /// <param name="editor">The editor.</param>
        private void InitSingleGraphitem(SingleGraphItem item, SingleGraphElementEditor editor)
        {
            TechGraphController controller = new TechGraphController();
            controller.Range.AutoY = true;
            controller.Range.MinimumY = item.TechMonitor.Min;
            controller.Range.MaximumY = item.TechMonitor.Max;
            controller.Range.MaximumX = TimeSpan.FromSeconds(10);

            controller.DataSeriesCollection.Add(new WpfGraphDataSeries()
            {
                Stroke = Colors.DodgerBlue,
            });

            editor.InnerGraph.Controller = controller;

            item.Editor = editor;

            _singleControllers.Add(item, controller);

            item.RecordingStarted += () =>
            {
                SaveFileDialog dlg = new SaveFileDialog();
                dlg.Title = "Save graph data as csv file";
                dlg.Filter = "CSV Files|*.csv";
                dlg.DefaultExt = ".csv";
                dlg.FileName = item.TechName;
                if (dlg.ShowDialog().Value)
                {
                    _single_graphs_recordings.Add(new SingleTechRecordingData(item.TechName, dlg.FileName) { Tag = item });
                    item.StartRecording();
                }
            };

            item.RecordingStopped += () =>
            {
                try
                {
                    item.StopRecording();

                    var graph_recording = _single_graphs_recordings.SingleOrDefault(x => x.Tag == item);

                    if (graph_recording != null)
                    {
                        _single_graphs_recordings.Remove(graph_recording);
                        graph_recording.Dispose();
                    }
                }
                catch (Exception ex)
                {
                    _notification.ShowError(LogManager.Log(ex).Message);
                }
            };
        }

        /// <summary>
        /// Initializes the multi graph item.
        /// </summary>
        /// <param name="item">The item.</param>
        /// <param name="editor">The editor.</param>
        private void InitMultiGraphItem(MultiGraphItem item, MultiGraphElementEditor editor)
        {
            TechGraphController controller = new TechGraphController(500);
            controller.Range.AutoY = true;
            controller.Range.MinimumY = item.TechMonitor.Min;
            controller.Range.MaximumY = item.TechMonitor.Max;
            controller.Range.MaximumX = TimeSpan.FromSeconds(10);

            for (int i = 0; i < item.TechMonitor.ChannelCount; i++)
            {
                controller.DataSeriesCollection.Add(new WpfGraphDataSeries()
                {
                    Stroke = ColorHelper.GetRandomColor(),
                    Name = item.TechMonitor.Name.First() + (i + 1).ToString(),
                });
            }

            editor.InnerGraph.Controller = controller;

            item.Editor = editor;

            _multiControllers.Add(item, controller);

            item.RecordingStarted += () =>
            {
                SaveFileDialog dlg = new SaveFileDialog();
                dlg.Title = "Save graph data as csv file";
                dlg.Filter = "CSV Files|*.csv";
                dlg.DefaultExt = ".csv";
                dlg.FileName = item.TechName;
                if (dlg.ShowDialog().Value)
                {
                    _multi_graph_recordings.Add(new MultiTechRecordingData(item.TechName, item.TechMonitor.ChannelCount, dlg.FileName) { Tag = item });
                    item.StartRecording();
                }
            };

            item.RecordingStopped += () =>
            {
                try
                {
                    item.StopRecording();

                    var graph_recording = _multi_graph_recordings.SingleOrDefault(x => x.Tag == item);

                    if (graph_recording != null)
                    {
                        _multi_graph_recordings.Remove(graph_recording);
                        graph_recording.Dispose();
                    }
                }
                catch (Exception ex)
                {
                    _notification.ShowError(LogManager.Log(ex).Message);
                }
            };
        }

        /// <summary>
        /// Initializes the thread motion item.
        /// </summary>
        /// <param name="item">The item.</param>
        private void InitThreadMotionItem(ThreadMotionItem item)
        {
            item.ActionExecuted += async (x, action) =>
            {
                try
                {
                    CheckMachineOperator();

                    if (action == MotorActionType.ForwardPressed)
                    {
                        await MachineOperator.StartThreadJogging(new ThreadJoggingRequest()
                        {
                            Speed = item.Speed,
                        });
                    }
                    else if (action == MotorActionType.ForwardReleased || action == MotorActionType.BackwardReleased)
                    {
                        await MachineOperator.StopThreadJogging(new ThreadAbortJoggingRequest());
                    }
                }
                catch (Exception ex)
                {
                    LogManager.Log(ex, String.Format(String.Format("Error executing technician command '{0}' on item '{1}'.", action, item.TechName)));
                }
            };
        }

        /// <summary>
        /// Initializes the motor group item.
        /// </summary>
        /// <param name="item">The item.</param>
        private void InitMotorGroupItem(MotorGroupItem item)
        {
            item.ActionExecuted += async (x, action) =>
            {
                try
                {
                    CheckMachineOperator();

                    if (action == MotorActionType.ForwardPressed)
                    {
                        await Task.WhenAll(item.TechMotors.Select(motor => MachineOperator.StartMotorJogging(new MotorJoggingRequest()
                        {
                            MotorType = (PMR.Hardware.HardwareMotorType)motor.Code,
                            Direction = MotorDirection.Forward,
                        })));
                    }
                    else if (action == MotorActionType.ForwardReleased)
                    {
                        await Task.WhenAll(item.TechMotors.Select(motor => MachineOperator.StopMotorJogging(new MotorAbortJoggingRequest()
                        {
                            MotorType = (PMR.Hardware.HardwareMotorType)motor.Code,
                        })));
                    }
                    else if (action == MotorActionType.BackwardPressed)
                    {
                        await Task.WhenAll(item.TechMotors.Select(motor => MachineOperator.StartMotorJogging(new MotorJoggingRequest()
                        {
                            MotorType = (PMR.Hardware.HardwareMotorType)motor.Code,
                            Direction = MotorDirection.Backward,
                        })));
                    }
                    else if (action == MotorActionType.BackwardReleased)
                    {
                        await Task.WhenAll(item.TechMotors.Select(motor => MachineOperator.StopMotorJogging(new MotorAbortJoggingRequest()
                        {
                            MotorType = (PMR.Hardware.HardwareMotorType)motor.Code,
                        })));
                    }
                    //else if (action == MotorActionType.HomingStarted)
                    //{
                    //    item.HomingProgress = 0;
                    //    item.IsHoming = true;
                    //    item.IsHomingCompleted = false;

                    //    MachineOperator.StartMotorHoming(new MotorHomingRequest()
                    //    {
                    //        Code = item.TechMotor.Code
                    //    })
                    //    .Subscribe((response) =>
                    //    {

                    //        item.HomingMaximumProgress = response.Message.MaxProgress;
                    //        item.HomingProgress = response.Message.Progress;

                    //    }, () =>
                    //    {

                    //        item.IsHoming = false;
                    //        item.IsHomingCompleted = true;

                    //    });
                    //}
                    //else if (action == MotorActionType.HomingStopped)
                    //{
                    //    await MachineOperator.StopMotorHoming(new MotorAbortHomingRequest()
                    //    {
                    //        Code = item.TechMotor.Code,
                    //    });

                    //    item.IsHoming = false;
                    //}
                }
                catch (Exception ex)
                {
                    LogManager.Log(ex, String.Format("Error executing technician command '{0}' on item '{1}'.", action, item.TechName));
                }
            };
        }

        /// <summary>
        /// Initializes the digital out item.
        /// </summary>
        /// <param name="item">The item.</param>
        private void InitDigitalOutItem(DigitalOutItem item)
        {
            item.ValueChanged += async (x, value) =>
            {
                try
                {
                    CheckMachineOperator();
                    await MachineOperator.SetDigitalOut(new SetDigitalOutRequest() { InterfaceIO = (InterfaceIOs)item.TechIo.Code, Value = value });
                }
                catch (Exception ex)
                {
                    LogManager.Log(ex, String.Format("Error executing technician set digital out command on '{0}'.", item.TechName));
                }
            };
        }

        /// <summary>
        /// Initializes the controller item.
        /// </summary>
        /// <param name="item">The controller item.</param>
        private void InitControllerItem(ControllerItem item)
        {
            item.SetCommandClicked += async (_, __) =>
            {
                try
                {
                    CheckMachineOperator();
                    await MachineOperator.SetComponentValue(new SetComponentValueRequest()
                    {
                        Component = (ValueComponent)item.TechController.Code,
                        Value = item.Value
                    });
                }
                catch (Exception ex)
                {
                    LogManager.Log(ex, String.Format("Error executing technician set value component command on '{0}'.", item.TechName));
                }
            };
        }

        /// <summary>
        /// Initializes the process parameter item.
        /// </summary>
        /// <param name="item">The item.</param>
        private void InitProcessParameterItem(ProcessParametersItem item)
        {
            item.PushParametersPressed += async (x, parameters) =>
            {
                try
                {
                    CheckMachineOperator();

                    using (_notification.PushTaskItem("Uploading process parameters..."))
                    {
                        try
                        {
                            await MachineOperator.UploadProcessParameters(parameters);
                        }
                        catch (Exception ex)
                        {
                            String msg = "Error uploading process parameters:" + Environment.NewLine + parameters.ToJsonString();
                            LogManager.Log(ex, msg);
                            _notification.ShowError("Could not upload process parameters." + Environment.NewLine + ex.Message);
                        }
                    }
                }
                catch (Exception ex)
                {
                    _notification.ShowError(ex.Message);
                }
            };
        }

        /// <summary>
        /// Initializes the job runner item.
        /// </summary>
        /// <param name="item">The item.</param>
        /// <exception cref="NotImplementedException"></exception>
        private void InitJobRunnerItem(JobRunnerItem item)
        {
            item.StartJob += async () =>
            {
                try
                {
                    CheckMachineOperator();

                    var handler = await MachineOperator.Print(item.Job, item.ProcessParameters);

                    item.JobHandler = handler;

                    handler.StatusChanged += (x, status) =>
                    {
                        item.RunningJobStatus = status;
                    };

                    handler.Stopped += (x, e) =>
                    {
                        item.IsJobStarted = false;
                    };
                }
                catch (Exception ex)
                {
                    item.IsJobStarted = false;
                    _notification.ShowError(ex.Message);
                }
            };

            item.StopJob += () =>
            {
                if (item.JobHandler != null)
                {
                    item.JobHandler.Cancel();
                    item.JobHandler = null;
                }
            };
        }

        /// <summary>
        /// Checks the machine operator.
        /// </summary>
        /// <exception cref="InvalidOperationException">No machine connected.</exception>
        private void CheckMachineOperator()
        {
            if (MachineOperator == null || MachineOperator.State != Transport.TransportComponentState.Connected)
            {
                throw new InvalidOperationException("No machine connected.");
            }
        }

        #endregion

        #region Project Management

        /// <summary>
        /// Opens a file open dialog to select a project file.
        /// </summary>
        public void OpenProject()
        {
            OpenFileDialog dlg = new OpenFileDialog();
            dlg.Title = "Select Technician Project File";
            dlg.Filter = "Technician Project File|*.tpf";

            if (dlg.ShowDialog().Value)
            {
                OpenProjectFile(dlg.FileName);
            }
        }

        /// <summary>
        /// Opens the specified project file path.
        /// </summary>
        /// <param name="fileName">File path.</param>
        public MachineTechViewProject OpenProjectFile(String fileName, bool load = true)
        {
            MachineTechViewProject project = null;

            try
            {
                project = MachineTechViewProject.Load(fileName);

                if (project.Tabs.Count == 0)
                {
                    LogManager.Log($"Error loading project file {fileName}. Trying to load using legacy project loader.", LogCategory.Warning);

                    MachineTechViewProjectTab tab = new MachineTechViewProjectTab();
                    tab.Name = "Untitled";
                    tab.Items.AddRange(project.Items);
                    project.Items.Clear();
                    project.Tabs.Add(tab);
                }

                if (load)
                {
                    LoadProject(project);
                    _lastTechProjectFile = fileName;
                }
            }
            catch (Exception ex)
            {
                LogManager.Log(ex, $"Error loading project file {fileName}.");
                _notification.ShowError("An error occurred while trying to load the tech board project file.");
            }

            return project;
        }

        /// <summary>
        /// Loads the specified project.
        /// </summary>
        /// <param name="project">The project.</param>
        public void LoadProject(MachineTechViewProject project, bool appendToCurrentProject = false)
        {
            using (_notification.PushTaskItem("Loading technician project file..."))
            {
                if (!appendToCurrentProject)
                {
                    Tabs.Clear();
                    _singleControllers.Clear();
                    _multiControllers.Clear();
                }

                foreach (var tab in project.Tabs)
                {
                    MachineTechTabVM t = new MachineTechTabVM();
                    t.Name = tab.Name;
                    Tabs.Add(t);

                    SelectedTab = t;

                    foreach (var item in tab.Items)
                    {
                        if (item is MotorGroupItem)
                        {
                            (item as MotorGroupItem).TechMotors = ObservablesStaticCollections.Instance.HardwareMotorTypes.Where(x => (item as MotorGroupItem).ItemsGuids.Contains(x.Guid)).ToObservableCollection();
                        }
                        else if (item is MonitorRecorderItem)
                        {
                            (item as MonitorRecorderItem).SetSelectedMonitors(ObservablesStaticCollections.Instance.TechMonitors.Where(x => (item as MonitorRecorderItem).SelectedMonitorsGuids.Contains(x.Guid)).ToList());
                        }

                        AddTechItem(item);
                    }
                }

                SelectedTab = Tabs.ElementAt(project.SelectedTabIndex);
            }
        }

        /// <summary>
        /// Opens the file save dialog for selecting a project file target.
        /// </summary>
        private void SaveAsProject()
        {
            SaveFileDialog dlg = new SaveFileDialog();
            dlg.Title = "Select Technician Project Location";
            dlg.Filter = "Technician Project File|*.tpf";

            if (dlg.ShowDialog().Value)
            {
                SaveProjectFile(dlg.FileName);
            }
        }

        /// <summary>
        /// Saves the current project to the specified file path.
        /// </summary>
        /// <param name="fileName">Name of the file.</param>
        private void SaveProjectFile(String fileName)
        {
            using (_notification.PushTaskItem("Saving technician project file..."))
            {
                MachineTechViewProject project = GenerateProjectFile();
                project.Save(fileName);
                _lastTechProjectFile = fileName;
            }
        }

        /// <summary>
        /// Saves the current opened project file. If not project file is opened will call <see cref="SaveAsProject"/>.
        /// </summary>
        private void SaveProject()
        {
            if (File.Exists(_lastTechProjectFile))
            {
                SaveProjectFile(_lastTechProjectFile);
            }
            else
            {
                SaveAsProject();
            }
        }

        /// <summary>
        /// Generates a project file from the current element setup.
        /// </summary>
        /// <returns></returns>
        private MachineTechViewProject GenerateProjectFile()
        {
            MachineTechViewProject project = new MachineTechViewProject();
            project.SelectedTabIndex = Tabs.IndexOf(SelectedTab);

            foreach (var tab in Tabs)
            {
                MachineTechViewProjectTab pTab = new MachineTechViewProjectTab();
                pTab.Name = tab.Name;

                foreach (var element in tab.Elements)
                {
                    if (element.HostedElement is MotorGroupItem)
                    {
                        var group = element.HostedElement as MotorGroupItem;
                        group.ItemsGuids = group.TechMotors.Select(x => x.Guid).ToList();
                    }

                    (element.HostedElement as TechItem).SetBounds(element.GetBounds());
                    pTab.Items.Add(element.HostedElement as TechItem);
                }

                project.Tabs.Add(pTab);
            }

            return project;
        }

        /// <summary>
        /// Removes the specified tab.
        /// </summary>
        /// <param name="tab">The tab.</param>
        private void RemoveTab(MachineTechTabVM tab)
        {
            if (_notification.ShowQuestion("Are you sure you want to delete the selected tab?"))
            {
                Tabs.Remove(tab);
                SelectedTab = Tabs.LastOrDefault();

                if (SelectedTab == null)
                {
                    AddNewTab("Untitled");
                }
            }
        }

        /// <summary>
        /// Adds a new tab.
        /// </summary>
        private bool AddNewTab(String name = null)
        {
            if (Tabs.Count > 7)
            {
                _notification.ShowError("Cannot exceed the maximum number of 8 tabs. You can remove a tab, or create a new project.");
                return false;
            }

            if (name == null)
            {
                name = _notification.ShowTextInput("Enter tab name", "Tab Name", "Untitled");
            }

            if (!String.IsNullOrWhiteSpace(name))
            {
                MachineTechTabVM t = new MachineTechTabVM();
                t.Name = name;
                Tabs.Add(t);
                SelectedTab = t;
                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// Creates a new project.
        /// </summary>
        private void CreateNewProject()
        {
            var to_remove = Tabs.ToList();

            var name = _notification.ShowTextInput("Enter project name", "Project Name", "untitled");

            if (String.IsNullOrWhiteSpace(name))
            {
                return;
            }

            SaveFileDialog dlg = new SaveFileDialog();
            dlg.Title = "Select Technician Project Location";
            dlg.Filter = "Technician Project File|*.tpf";
            dlg.DefaultExt = ".tpf";
            dlg.FileName = name;

            if (!dlg.ShowDialog().Value)
            {
                return;
            }

            _singleControllers.Clear();
            _multiControllers.Clear();
            _multi_graph_recordings.Clear();
            _multi_monitors_recordings.Clear();
            _single_graphs_recordings.Clear();
            _single_monitors_recordings.Clear();

            foreach (var tab in to_remove)
            {
                Tabs.Remove(tab);
            }

            AddNewTab(name);

            _lastTechProjectFile = dlg.FileName;
            File.AppendAllText(_lastTechProjectFile, "");
            SaveProject();
        }

        /// <summary>
        /// Renames the tab.
        /// </summary>
        /// <param name="tab">The tab.</param>
        private void RenameTab()
        {
            if (SelectedTab != null)
            {
                var name = _notification.ShowTextInput("Enter tab name", "Tab Name", SelectedTab.Name);

                if (!String.IsNullOrWhiteSpace(name))
                {
                    SelectedTab.Name = name;
                }
            }
        }

        /// <summary>
        /// Imports a project file tabs to the current project.
        /// </summary>
        private void ImportProjectTabs()
        {
            OpenFileDialog dlg = new OpenFileDialog();
            dlg.Title = "Select Technician Project File";
            dlg.Filter = "Technician Project File|*.tpf";

            if (dlg.ShowDialog().Value)
            {
                var project = OpenProjectFile(dlg.FileName, false);

                if (project != null)
                {
                    var vm = new ImportProjectTabViewVM(project.Tabs, Path.GetFileNameWithoutExtension(dlg.FileName));
                    _notification.ShowModalDialog<ImportProjectTabViewVM, ImportProjectTabView>(vm, (x) =>
                    {
                        project.Tabs.Clear();
                        project.Tabs.AddRange(vm.Tabs.SynchedSource.ToList());
                        LoadProject(project, true);

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

        #endregion

        #region IStudioModuleVM

        public override void OnNavigatedTo()
        {
            base.OnNavigatedTo();
            //_singleControllers.ToList().ForEach(x => x.Value.ChangeRenderMode(true));
        }

        public override void OnNavigatedFrom()
        {
            base.OnNavigatedFrom();
            //_singleControllers.ToList().ForEach(x => x.Value.ChangeRenderMode(false));
        }

        public override void OnShuttingDown()
        {
            InvokeUINow(() =>
            {
                _settings.LastTechProjectFile = _lastTechProjectFile;
            });
        }

        public override void OnApplicationReady()
        {
            if (_populateFramesThread == null)
            {
                _populateFramesThread = new Thread(PopulateFramesThreadMethod);
                _populateFramesThread.IsBackground = true;
                _populateFramesThread.Start();
            }
        }

        #endregion

        #region Hardware Configuration

        private void SyncHardwareConfiguration()
        {
            var elements = Tabs.SelectMany(x => x.Elements).ToList();

            if (MachineOperator != null && MachineOperator.CurrentHardwareConfiguration != null)
            {
                var config = MachineOperator.CurrentHardwareConfiguration;

                foreach (var motorConfig in config.Motors)
                {
                    var itemConfig = MotorItem.MotorConfigurations.SingleOrDefault(x => x.HardwareMotorType.Code == motorConfig.HardwareMotorType.ToInt32());

                    if (itemConfig != null)
                    {
                        motorConfig.MapPrimitivesTo(itemConfig);
                    }
                }

                foreach (var pidConfig in config.PidControls)
                {
                    var itemConfig = PidItem.PidConfigurations.SingleOrDefault(x => x.HardwarePidControlType.Code == pidConfig.HardwarePidControlType.ToInt32());

                    if (itemConfig != null)
                    {
                        pidConfig.MapPrimitivesTo(itemConfig);
                    }
                }

                foreach (var winderConfig in config.Winders)
                {
                    var itemConfig = WinderItem.WinderConfigurations.SingleOrDefault(x => x.HardwareWinderType.Code == winderConfig.HardwareWinderType.ToInt32());

                    if (itemConfig != null)
                    {
                        winderConfig.MapPrimitivesTo(itemConfig);
                    }
                }

                foreach (var dancerConfig in config.Dancers)
                {
                    var itemConfig = DancerItem.DancerConfigurations.SingleOrDefault(x => x.HardwareDancerType.Code == dancerConfig.HardwareDancerType.ToInt32());

                    if (itemConfig != null)
                    {
                        dancerConfig.MapPrimitivesTo(itemConfig);
                    }
                }

                foreach (var speedSensorConfig in config.SpeedSensors)
                {
                    var itemConfig = SpeedSensorItem.SpeedSensorConfigurations.SingleOrDefault(x => x.HardwareSpeedSensorType.Code == speedSensorConfig.HardwareSpeedSensorType.ToInt32());

                    if (itemConfig != null)
                    {
                        speedSensorConfig.MapPrimitivesTo(itemConfig);
                    }
                }

                foreach (var blowerConfig in config.Blowers)
                {
                    var itemConfig = BlowerItem.BlowerConfigurations.SingleOrDefault(x => x.HardwareBlowerType.Code == blowerConfig.HardwareBlowerType.ToInt32());

                    if (itemConfig != null)
                    {
                        blowerConfig.MapPrimitivesTo(itemConfig);
                    }
                }

                foreach (var breakSensorConfig in config.BreakSensors)
                {
                    var itemConfig = BreakSensorItem.BreakSensorConfigurations.SingleOrDefault(x => x.HardwareBreakSensorType.Code == breakSensorConfig.HardwareBreakSensorType.ToInt32());

                    if (itemConfig != null)
                    {
                        breakSensorConfig.MapPrimitivesTo(itemConfig);
                    }
                }

                _notification.ShowInfo("Visual elements synced to the last uploaded hardware configuration.");
            }
            else
            {
                ResetHardwareConfiguration();
            }
        }

        private async void UploadHardwareConfiguration()
        {
            if (MachineOperator != null)
            {
                using (_notification.PushTaskItem("Uploading hardware configuration..."))
                {
                    try
                    {
                        HardwareVersion hw = null;
                        Configuration config = null;

                        await Task.Factory.StartNew(() =>
                        {
                            using (ObservablesContext db = ObservablesContext.CreateDefault())
                            {
                                config = db.Adapter.GetConfiguration(x => x.Guid == ApplicationManager.Machine.ConfigurationGuid);
                                hw = db.Adapter.GetHardwareVersionByMachine(ApplicationManager.Machine.Guid).Clone();
                            }
                        });

                        foreach (var motorConfig in hw.HardwareMotors)
                        {
                            var itemConfig = MotorItem.MotorConfigurations.SingleOrDefault(x => x.HardwareMotorType.Code == motorConfig.HardwareMotorType.Code);

                            if (itemConfig != null)
                            {
                                itemConfig.MapPrimitivesTo(motorConfig);
                            }
                        }

                        foreach (var pidConfig in hw.HardwarePidControls)
                        {
                            var itemConfig = PidItem.PidConfigurations.SingleOrDefault(x => x.HardwarePidControlType.Code == pidConfig.HardwarePidControlType.Code);

                            if (itemConfig != null)
                            {
                                itemConfig.MapPrimitivesTo(pidConfig);
                            }
                        }

                        foreach (var winderConfig in hw.HardwareWinders)
                        {
                            var itemConfig = WinderItem.WinderConfigurations.SingleOrDefault(x => x.HardwareWinderType.Code == winderConfig.HardwareWinderType.Code);

                            if (itemConfig != null)
                            {
                                itemConfig.MapPrimitivesTo(winderConfig);
                            }
                        }

                        foreach (var dancerConfig in hw.HardwareDancers)
                        {
                            var itemConfig = DancerItem.DancerConfigurations.SingleOrDefault(x => x.HardwareDancerType.Code == dancerConfig.HardwareDancerType.Code);

                            if (itemConfig != null)
                            {
                                itemConfig.MapPrimitivesTo(dancerConfig);
                            }
                        }

                        foreach (var speedSensorConfig in hw.HardwareSpeedSensors)
                        {
                            var itemConfig = SpeedSensorItem.SpeedSensorConfigurations.SingleOrDefault(x => x.HardwareSpeedSensorType.Code == speedSensorConfig.HardwareSpeedSensorType.Code);

                            if (itemConfig != null)
                            {
                                itemConfig.MapPrimitivesTo(speedSensorConfig);
                            }
                        }

                        foreach (var blowerConfig in hw.HardwareBlowers)
                        {
                            var itemConfig = BlowerItem.BlowerConfigurations.SingleOrDefault(x => x.HardwareBlowerType.Code == blowerConfig.HardwareBlowerType.Code);

                            if (itemConfig != null)
                            {
                                itemConfig.MapPrimitivesTo(blowerConfig);
                            }
                        }

                        foreach (var breakSensorConfig in hw.HardwareBreakSensors)
                        {
                            var itemConfig = BreakSensorItem.BreakSensorConfigurations.SingleOrDefault(x => x.HardwareBreakSensorType.Code == breakSensorConfig.HardwareBreakSensorType.Code);

                            if (itemConfig != null)
                            {
                                itemConfig.MapPrimitivesTo(breakSensorConfig);
                            }
                        }

                        await MachineOperator.UploadHardwareConfiguration(hw, config);
                    }
                    catch (Exception ex)
                    {
                        LogManager.Log(ex, "Error uploading hardware configuration.");
                        _notification.ShowError("An error occurred while trying to upload the hardware configuration." + Environment.NewLine + ex.Message);
                    }
                }
            }
        }

        private async void UploadPartialHardwareConfiguration()
        {
            if (MachineOperator != null)
            {
                if (_notification.ShowQuestion("This will upload only visible elements across all tabs as a hardware configuration package. Are you sure?"))
                {
                    using (_notification.PushTaskItem("Uploading partial hardware configuration..."))
                    {
                        try
                        {
                            HardwareVersion hw = new HardwareVersion();
                            hw.Name = "Partial HW Configuration";
                            hw.Version = 1;

                            Configuration config = null;

                            foreach (var element in Tabs.SelectMany(x => x.Elements).ToList())
                            {
                                var item = element.HostedElement as TechItem;

                                if (item is MotorItem)
                                {
                                    hw.HardwareMotors.Add((item as MotorItem).HardwareMotor);
                                }
                                else if (item is DancerItem)
                                {
                                    hw.HardwareDancers.Add((item as DancerItem).HardwareDancer);
                                }
                                else if (item is PidItem)
                                {
                                    hw.HardwarePidControls.Add((item as PidItem).HardwarePid);
                                }
                                else if (item is BlowerItem)
                                {
                                    hw.HardwareBlowers.Add((item as BlowerItem).HardwareBlower);
                                }
                                else if (item is BreakSensorItem)
                                {
                                    hw.HardwareBreakSensors.Add((item as BreakSensorItem).HardwareBreakSensor);
                                }
                                else if (item is SpeedSensorItem)
                                {
                                    hw.HardwareSpeedSensors.Add((item as SpeedSensorItem).HardwareSpeedSensor);
                                }
                                else if (item is WinderItem)
                                {
                                    hw.HardwareWinders.Add((item as WinderItem).HardwareWinder);
                                }
                            }

                            await Task.Factory.StartNew(() =>
                            {
                                using (ObservablesContext db = ObservablesContext.CreateDefault())
                                {
                                    config = db.Adapter.GetConfiguration(x => x.Guid == ApplicationManager.Machine.ConfigurationGuid).Clone();
                                }
                            });

                            await MachineOperator.UploadHardwareConfiguration(hw, config);
                        }
                        catch (Exception ex)
                        {
                            LogManager.Log(ex, "Error uploading partial hardware configuration.");
                            _notification.ShowError("An error occurred while trying to upload the partial hardware configuration." + Environment.NewLine + ex.Message);
                        }
                    }
                }
            }
        }

        private async void ResetHardwareConfiguration(bool showMessage = true)
        {
            if (MachineOperator != null)
            {
                ObservablesContext db = ObservablesContext.CreateDefault();
                ObservablesContextAdapter adapter = new ObservablesContextAdapter(db);

                HardwareVersion hw = null;

                await Task.Factory.StartNew(() =>
                {
                    hw = adapter.GetHardwareVersionByMachine(ApplicationManager.Machine.Guid);
                });

                foreach (var motorConfig in hw.HardwareMotors)
                {
                    var itemConfig = MotorItem.MotorConfigurations.SingleOrDefault(x => x.HardwareMotorType.Code == motorConfig.HardwareMotorType.Code);

                    if (itemConfig != null)
                    {
                        motorConfig.MapPrimitivesTo(itemConfig);
                    }
                }

                foreach (var pidConfig in hw.HardwarePidControls)
                {
                    var itemConfig = PidItem.PidConfigurations.SingleOrDefault(x => x.HardwarePidControlType.Code == pidConfig.HardwarePidControlType.Code);

                    if (itemConfig != null)
                    {
                        pidConfig.MapPrimitivesTo(itemConfig);
                    }
                }

                foreach (var winderConfig in hw.HardwareWinders)
                {
                    var itemConfig = WinderItem.WinderConfigurations.SingleOrDefault(x => x.HardwareWinderType.Code == winderConfig.HardwareWinderType.Code);

                    if (itemConfig != null)
                    {
                        winderConfig.MapPrimitivesTo(itemConfig);
                    }
                }

                foreach (var dancerConfig in hw.HardwareDancers)
                {
                    var itemConfig = DancerItem.DancerConfigurations.SingleOrDefault(x => x.HardwareDancerType.Code == dancerConfig.HardwareDancerType.Code);

                    if (itemConfig != null)
                    {
                        dancerConfig.MapPrimitivesTo(itemConfig);
                    }
                }

                foreach (var speedSensorConfig in hw.HardwareSpeedSensors)
                {
                    var itemConfig = SpeedSensorItem.SpeedSensorConfigurations.SingleOrDefault(x => x.HardwareSpeedSensorType.Code == speedSensorConfig.HardwareSpeedSensorType.Code);

                    if (itemConfig != null)
                    {
                        speedSensorConfig.MapPrimitivesTo(itemConfig);
                    }
                }

                foreach (var blowerSensorConfig in hw.HardwareBlowers)
                {
                    var itemConfig = BlowerItem.BlowerConfigurations.SingleOrDefault(x => x.HardwareBlowerType.Code == blowerSensorConfig.HardwareBlowerType.Code);

                    if (itemConfig != null)
                    {
                        blowerSensorConfig.MapPrimitivesTo(itemConfig);
                    }
                }

                foreach (var breakSensorConfig in hw.HardwareBreakSensors)
                {
                    var itemConfig = BreakSensorItem.BreakSensorConfigurations.SingleOrDefault(x => x.HardwareBreakSensorType.Code == breakSensorConfig.HardwareBreakSensorType.Code);

                    if (itemConfig != null)
                    {
                        breakSensorConfig.MapPrimitivesTo(itemConfig);
                    }
                }

                if (showMessage)
                {
                    _notification.ShowInfo("Visual elements synced to hardware version " + hw.Name + ", " + hw.Version + ".");
                }
            }
        }

        #endregion

        #region Graphs

        public void ClearAllGraphs()
        {
            _start_time = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, 0, 0, 0);
            _last_time = DateTime.Now;

            foreach (var controller in _singleControllers)
            {
                controller.Value.Clear();
            }

            foreach (var controller in _multiControllers)
            {
                controller.Value.Clear();
            }
        }

        #endregion
    }
}