using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Drawing; using System.IO; using System.Linq; using System.Reactive.Concurrency; using System.Reactive.Linq; using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows; using Google.Protobuf; using Tango.Core; using Tango.Core.DI; using Tango.Core.ExtensionMethods; using Tango.Core.Threading; using Tango.FSE.Common.Connection; using Tango.FSE.Common.Diagnostics; using Tango.FSE.Common.Notifications; using Tango.FSE.Common.Threading; using Tango.FSE.Procedures.Dialogs; using Tango.Integration.Operation; using Tango.PMR; using Tango.Scripting.Basic; namespace Tango.FSE.Procedures { public class ProcedureContext : IProcedureContext { private IProcedureLogger _logger; private ProcedureProject _project; private Dictionary _inputs; private DiagnosticsFrame _lastDiagnosticsFrame; [TangoInject] private IMachineProvider MachineProvider { get; set; } [TangoInject] private INotificationProvider NotificationProvider { get; set; } [TangoInject] private IDispatcherProvider DispatcherProvider { get; set; } [TangoInject] private IDiagnosticsProvider DiagnosticsProvider { get; set; } public ReadOnlyCollection Results { get; private set; } public ProcedureContext(ProcedureProject project, IProcedureLogger logger) { _project = project; _inputs = new Dictionary(); foreach (var input in _project.Inputs) { _inputs.Add(input.Key, input); } _logger = logger; Results = new ReadOnlyCollection(new List()); TangoIOC.Default.Inject(this); } public IMessage Send(string messageName, int? timeout = null, params object[] args) { var stubType = MessageFactory.GetAvailableRequestStubs().SingleOrDefault(x => x.Name.ToLower() == messageName.ToLower() || x.Name.Replace("Request", "").ToLower() == messageName.ToLower()); if (stubType == null) { throw new ArgumentException("Invalid stub '" + messageName + "'."); } var stubProps = stubType.GetProperties(BindingFlags.Public | BindingFlags.Instance); if (stubProps.Length > args.Length) { throw new ArgumentOutOfRangeException("Not enough arguments for " + stubType.Name + "."); } Object request = Activator.CreateInstance(stubType); int argIndex = 0; foreach (var prop in stubProps) { Object arg = args[argIndex++]; if (prop.PropertyType == typeof(UInt32)) { prop.SetValue(request, UInt32.Parse(arg.ToString())); } else if (prop.PropertyType == typeof(bool)) { prop.SetValue(request, bool.Parse(arg.ToString())); } else if (prop.PropertyType.IsPrimitive) { object converted = Convert.ChangeType(arg, prop.PropertyType); prop.SetValue(request, converted); } else { prop.SetValue(request, arg); } } return Send(request as IMessage, timeout); } public IMessage Send(string messageName, params object[] args) { return Send(messageName, null, args); } public T Send(string messageName, int? timeout = null, params object[] args) where T : class, IMessage { return Send(messageName, timeout, args) as T; } public T Send(string messageName, params object[] args) where T : class, IMessage { return Send(messageName, null, args); } public IMessage Send(IMessage message, int? timeout = null) { TimeSpan? timespan = null; if (timeout != null) { timespan = TimeSpan.FromMilliseconds(timeout.Value); } return MachineProvider.MachineOperator.SendRequest(message, new Transport.TransportRequestConfig() { Timeout = timespan, }).Result; } public T Send(IMessage messageName, int? timeout = null) where T : class, IMessage { return Send(messageName, timeout) as T; } public void SendContinuous(IMessage messageName, Action callback, int? timeout = null) where T : class, IMessage { TaskCompletionSource completion = new TaskCompletionSource(); TimeSpan? timespan = null; if (timeout != null) { timespan = TimeSpan.FromMilliseconds(timeout.Value); } MachineProvider.MachineOperator.SendContinuousRequest(messageName, new Transport.TransportContinuousRequestConfig() { Timeout = timespan, ContinuousTimeout = timespan }).ObserveOn(new NewThreadScheduler()).Subscribe((msg) => { try { callback?.Invoke(msg as T); } catch { } }, (ex) => { completion.SetException(ex); }, () => { completion.SetResult(true); }); completion.Task.GetAwaiter().GetResult(); } public void SendContinuous(string messageName, Action callback, int? timeout = null, params object[] args) where T : class, IMessage { var stubType = MessageFactory.GetAvailableRequestStubs().SingleOrDefault(x => x.Name.ToLower() == messageName.ToLower() || x.Name.Replace("Request", "").ToLower() == messageName.ToLower()); if (stubType == null) { throw new ArgumentException("Invalid stub '" + messageName + "'."); } var stubProps = stubType.GetProperties(BindingFlags.Public | BindingFlags.Instance); if (stubProps.Length > args.Length) { throw new ArgumentOutOfRangeException("Not enough arguments for " + stubType.Name + "."); } IMessage request = Activator.CreateInstance(stubType) as IMessage; int argIndex = 0; foreach (var prop in stubProps) { Object arg = args[argIndex++]; if (prop.PropertyType == typeof(UInt32)) { prop.SetValue(request, UInt32.Parse(arg.ToString())); } else if (prop.PropertyType == typeof(bool)) { prop.SetValue(request, bool.Parse(arg.ToString())); } else if (prop.PropertyType.IsPrimitive) { object converted = Convert.ChangeType(arg, prop.PropertyType); prop.SetValue(request, converted); } else { prop.SetValue(request, arg); } } SendContinuous(request, callback as Action, timeout); } public void SendContinuous(string messageName, Action callback, params object[] args) where T : class, IMessage { SendContinuous(messageName, callback, null, args); } public void WriteLine(object obj) { String line = "null"; if (obj != null) { if (!obj.GetType().IsValueTypeOrString()) { line = obj.ToJsonString(); } else { line = obj.ToString(); } } _logger?.WriteLine(line); } public void Write(object obj) { String line = "null"; if (obj != null) { if (obj.GetType().IsClass) { line = obj.ToJsonString(); } else { line = obj.ToString(); } } _logger?.WriteLine(line); } public void WriteLineHex(object number, int digits) { _logger?.WriteLine("#" + Convert.ToInt32(number).ToString("X" + digits.ToString())); } public void WriteHex(object number, int digits) { _logger?.Write("#" + Convert.ToInt32(number).ToString("X" + digits.ToString())); } public void Clear() { _logger?.Clear(); } public void WriteToFile(string filePath, string content) { File.WriteAllText(filePath, content); } public void AppendToFile(string filePath, string content) { File.AppendAllText(filePath, content); } public Result AddResult(ResultType type, string name, object value) { var result = new Result(type, name, value); return AddResult(result); } public Result AddGraphResult(ResultType type, string name, IEnumerable values) { return AddResult(new Result() { IsGraph = true, Name = name, Type = type, Value = values, }); } public Result AddResult(Result result) { List results = new List(Results); results.Add(result); Results = new ReadOnlyCollection(results); return result; } public void RemoveResult(Result result) { List results = new List(Results); results.Remove(result); Results = new ReadOnlyCollection(results); } public void ClearResults() { Results = new ReadOnlyCollection(new List()); } public T GetInput(string key) { var value = GetInput(key); try { return (T)Convert.ChangeType(value, typeof(T)); } catch { throw new InvalidCastException($"Error converting the specified input '{value}' to type '{typeof(T).Name}'."); } } public List GetInputArray(String key) { var value = GetInput(key); String[] arr = value.ToStringSafe().Split(','); var list = new List(arr.Select(x => (T)Convert.ChangeType(x, typeof(T)))); return list; } public object GetInput(string key) { ProcedureInput input = null; if (_inputs.TryGetValue(key, out input)) { return input.Value; } else { throw new KeyNotFoundException($"Could no find input with key '{key}'."); } } public void Fail(string message) { throw new ProcedureFailedException(message); } public void ShowInfo(string message) { AutoInvoke(NotificationProvider.ShowInfo(message)); } public void ShowWarning(string message) { AutoInvoke(NotificationProvider.ShowWarning(message)); } public void ShowError(string message) { AutoInvoke(NotificationProvider.ShowError(message)); } public bool ShowQuestion(string message) { return AutoInvoke(NotificationProvider.ShowQuestion(message)); } public bool ShowWarningQuestion(string message) { return AutoInvoke(NotificationProvider.ShowWarningQuestion(message)); } public T RequestUserInputFor(string title, string message) { return RequestUserInputFor(typeof(T).IsValueTypeOrString() ? default(T) : Activator.CreateInstance(), title, message); } public T RequestUserInputFor(T model, string title, string message) { Object obj = model; if (typeof(T) == typeof(String) && model == null) { obj = ""; } UserInputDialogViewVM vm = new UserInputDialogViewVM(title, message, obj); vm.Init(); TaskCompletionSource completion = new TaskCompletionSource(); DispatcherProvider.Invoke(async () => { await NotificationProvider.ShowDialog(vm); completion.SetResult(obj); }); var result = completion.Task.GetAwaiter().GetResult(); vm.FinalizeModel(); return (T)vm.Model; } public void WriteLineArray(IEnumerable array, ArrayParsingStyle style) { String line = String.Empty; List list = new List(); foreach (var item in array) { list.Add(item); } if (style == ArrayParsingStyle.Comma) { line = String.Join(", ", list.Select(x => x.ToStringSafe())); } else { foreach (var item in list) { line += $"[{item.ToStringSafe()}] "; } } WriteLine(line); } public DiagnosticsPackage GetDiagnosticsPackage(bool waitForNext = false) { if (waitForNext) { Stopwatch watch = new Stopwatch(); watch.Start(); while (_lastDiagnosticsFrame == DiagnosticsProvider.CurrentFrame) { Thread.Sleep(10); if (watch.Elapsed.TotalSeconds > 5) { throw new TimeoutException("Diagnostics package did not arrive within the given time of 5 seconds."); } } } _lastDiagnosticsFrame = DiagnosticsProvider.CurrentFrame; return DiagnosticsProvider.CurrentFrame.ToPackage(); } public void Sleep(int milliseconds) { Thread.Sleep(milliseconds); } public Thread RunAsync(Action action) { return ThreadFactory.StartNew(action); } public string GetFileText(string path) { return File.ReadAllText(path); } public byte[] GetFileBytes(string path) { return File.ReadAllBytes(path); } public String RequestFileOpen(string message, string extension = "*.*") { OpenFileDialogViewVM vm = new OpenFileDialogViewVM(message, extension); TaskCompletionSource completion = new TaskCompletionSource(); DispatcherProvider.Invoke(async () => { await NotificationProvider.ShowDialog(vm); completion.SetResult(vm.SelectedFile); }); var result = completion.Task.GetAwaiter().GetResult(); return result; } public string RequestFileSave(string message, string extension = "*.*", string defaultFileName = null) { SaveFileDialogViewVM vm = new SaveFileDialogViewVM(message, extension, defaultFileName); TaskCompletionSource completion = new TaskCompletionSource(); DispatcherProvider.Invoke(async () => { await NotificationProvider.ShowDialog(vm); completion.SetResult(vm.SelectedFile); }); var result = completion.Task.GetAwaiter().GetResult(); return result; } public void UpdateProgress(string message, bool isIndeterminate = true, double value = 0, double maximum = 100) { Progress?.Invoke(this, new TangoProgressChangedEventArgs() { Progress = new TangoProgress() { IsIndeterminate = isIndeterminate, Message = message, Maximum = maximum, Value = value }, }); } public event EventHandler> Progress; public Result AddBitmapResult(ResultType type, String name, Bitmap bitmap) { return AddResult(new Result() { IsBitmap = true, Name = name, Type = type, Value = bitmap }); } public Bitmap CreateBitmap(int width, int height) { return new Bitmap(width, height); } public Graphics GetDrawingContext(Bitmap bitmap) { return Graphics.FromImage(bitmap); } public IDialogController LoadDialog(string name) { var dialog = GetDialog(name); if (dialog == null) { throw new FileNotFoundException($"The specified dialog '{name}' could not be found in the project."); } DialogController controller = new DialogController(dialog.Xaml); controller.Init(); return controller; } public IDialogController LoadDialogAsWindow(string name, String windowTitle) { var dialog = GetDialog(name); if (dialog == null) { throw new FileNotFoundException($"The specified dialog '{name}' could not be found in the project."); } WindowController controller = new WindowController(dialog.Xaml, windowTitle); controller.Init(); return controller; } private ProcedureDialog GetDialog(String name) { name = Path.GetFileNameWithoutExtension(name); return _project.Dialogs.SingleOrDefault(x => x.Name.ToLower() == name.ToLower() + ".xaml"); } private void AutoInvoke(Task task) { bool completed = false; if (Application.Current.Dispatcher.Thread == Thread.CurrentThread) { task.ContinueWith((x) => { completed = true; }); DispatcherProvider.Invoke((Action)(async () => { await task; })); while (!completed) { Thread.Sleep(2); DispatcherProvider.DoEvents(); } } else { task.ContinueWith((x) => { completed = true; }); DispatcherProvider.Invoke((Action)(async () => { await task; })); while (!completed) { Thread.Sleep(10); } } } private T AutoInvoke(Task task) { bool completed = false; T result = default(T); if (Application.Current.Dispatcher.Thread == Thread.CurrentThread) { task.ContinueWith((x) => { result = x.Result; completed = true; }); DispatcherProvider.Invoke(async () => { await task; }); while (!completed) { Thread.Sleep(2); DispatcherProvider.DoEvents(); } return result; } else { task.ContinueWith((x) => { result = x.Result; completed = true; }); DispatcherProvider.Invoke(async () => { await task; }); while (!completed) { Thread.Sleep(10); } return result; } } } }