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.Runtime.CompilerServices; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows; using Google.Protobuf; using Tango.BL.Entities; using Tango.Core; using Tango.Core.DI; using Tango.Core.ExtensionMethods; using Tango.Core.Threading; using Tango.CSV; using Tango.FSE.Common.Connection; using Tango.FSE.Common.DataStore; using Tango.FSE.Common.Diagnostics; using Tango.FSE.Common.Notifications; using Tango.FSE.Common.Threading; using Tango.FSE.Procedures.Dialogs; using Tango.FSE.Procedures.Helpers; using Tango.Integration.Operation; using Tango.PMR; using Tango.PPC.Shared.Notifications; using Tango.Scripting.Basic; using Tango.Scripting.Core; namespace Tango.FSE.Procedures { public class ProcedureContext : ExtendedObject, IProcedureContext { private IProcedureLogger _logger; private ProcedureProject _project; private Dictionary _inputs; private DiagnosticsFrame _lastDiagnosticsFrame; public event EventHandler> Progress; public event EventHandler BreakPointRequest; [TangoInject] private IMachineProvider MachineProvider { get; set; } [TangoInject] private INotificationProvider NotificationProvider { get; set; } [TangoInject] private IDispatcherProvider DispatcherProvider { get; set; } [TangoInject] private IDiagnosticsProvider DiagnosticsProvider { get; set; } [TangoInject] private IRemoteDataStoreManager RemoteDataStore { get; set; } public ReadOnlyCollection Results { get; private set; } public MachineConnectionTypes ConnectionType { get { return MachineProvider.ConnectionType; } } public bool IsConnected { get { return MachineProvider.IsConnected; } } public Machine ConnectedMachine { get { return MachineProvider.Machine; } } public Action OnExitAction { get; 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, TimeSpan? 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($"Stub '{messageName}' could not be located on the PMR."); } 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 != null ? (int?)timeout.Value.TotalSeconds : null); } public IMessage Send(string messageName, params object[] args) { return Send(messageName, null, args); } public T Send(string messageName, TimeSpan? 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 virtual 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, ThreadingMode = Transport.TransportThreadingMode.ThreadPool }).Result; } public T Send(IMessage messageName, int? timeout = null) where T : class, IMessage { return Send(messageName, timeout) as T; } public virtual void SendContinuous(IMessage message, 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(message, new Transport.TransportContinuousRequestConfig() { Timeout = timespan, ContinuousTimeout = timespan, ThreadingMode = Transport.TransportThreadingMode.ThreadPool }).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, TimeSpan? 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 != null ? (int?)timeout.Value.TotalSeconds : null); } public void SendContinuous(string messageName, Action callback, params object[] args) where T : class, IMessage { SendContinuous(messageName, callback, null, args); } public void WriteLine() { _logger?.WriteLine(String.Empty); } 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().IsValueTypeOrString()) { 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) { EnsureFileLocation(filePath); File.WriteAllText(filePath, content); } public void WriteToFile(string filePath, byte[] data) { EnsureFileLocation(filePath); File.WriteAllBytes(filePath, data); } public void AppendToFile(string filePath, string content) { EnsureFileLocation(filePath); 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 void ShowInfoRemote(string message, int timeout) { ShowRemoteMessageBox(RemoteMessageBoxType.Info, message, timeout); } public void ShowWarningRemote(string message, int timeout) { ShowRemoteMessageBox(RemoteMessageBoxType.Warning, message, timeout); } public void ShowErrorRemote(string message, int timeout) { ShowRemoteMessageBox(RemoteMessageBoxType.Error, message, timeout); } public bool ShowQuestionRemote(string message, int timeout) { return ShowRemoteMessageBox(RemoteMessageBoxType.Question, message, timeout); } private bool ShowRemoteMessageBox(RemoteMessageBoxType type, String message, int timeout) { TimeSpan timespan = TimeSpan.FromSeconds(timeout); var response = MachineProvider.MachineOperator.SendGenericRequest< RemoteMessageBoxRequest, RemoteMessageBoxResponse>( new RemoteMessageBoxRequest() { Type = type, Timeout = timespan, Message = message, }, new Transport.TransportRequestConfig() { Timeout = timespan.Add(TimeSpan.FromSeconds(2)) }).Result; return response.Result; } 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 string RequestFolderSelect(string message) { SelectFolderDialogViewVM vm = new SelectFolderDialogViewVM(message); TaskCompletionSource completion = new TaskCompletionSource(); DispatcherProvider.Invoke(async () => { await NotificationProvider.ShowDialog(vm); completion.SetResult(vm.SelectedFolder); }); 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 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 IDialogWindowController 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; } } public T GetService() { return TangoIOC.Default.GetInstance(); } public byte[] GetResourceBytes(string resourceName) { var resource = _project.Resources.SingleOrDefault(x => x.Name.ToLower() == resourceName.ToLower()); if (resource == null) { throw new FileNotFoundException($"The specified resource '{resourceName}' could not be found on the procedure project."); } return resource.Data; } public string GetResourceString(string resourceName) { byte[] data = GetResourceBytes(resourceName); return Encoding.Default.GetString(data); } public void OpenResource(string resourceName) { var data = GetResourceBytes(resourceName); var tempFolder = TemporaryManager.CreateFolder(); var tempFile = Path.Combine(tempFolder, resourceName); File.WriteAllBytes(tempFile, data); Process.Start(tempFile); } public object GetVariable(string name) { ProcedureVariable variable = _project.Variables.SingleOrDefault(x => x.Name == name); if (variable == null) { throw new KeyNotFoundException($"The specified variable '{name}' could not found."); } return variable.Value; } public T GetVariable(string name) { object value = GetVariable(name); try { return (T)Convert.ChangeType(value, typeof(T)); } catch { throw new InvalidCastException($"Error converting the specified variable value '{value}' to type '{typeof(T).Name}'."); } } public List GetVariableArray(string name) { object value = GetVariable(name); String[] arr = value.ToStringSafe().Split(','); var list = new List(arr.Select(x => (T)Convert.ChangeType(x, typeof(T)))); return list; } public List ReadCsv(string file) where T : class, new() { var items = CsvFile.Read(new CsvSource(file)); return items.ToList(); } public List ReadCsv(byte[] data) where T : class, new() { using (MemoryStream ms = new MemoryStream(data)) { var items = CsvFile.Read(new CsvSource(ms)); return items.ToList(); } } public void WriteCsv(string file, List items) { CsvFile csvFile = new CsvFile(new CsvDestination(file)); foreach (var item in items) { csvFile.Append(item); } csvFile.Dispose(); } public IRemoteDataStoreManager GetRemoteDataStoreManager() { return RemoteDataStore; } public void BreakPoint(string file, int lineNumber, params object[] symbolsMap) { if (BreakPointRequest != null && symbolsMap.Length > 0) { bool released = false; BreakPointRequestEventArgs args = new BreakPointRequestEventArgs(() => { released = true; }); args.LineNumber = lineNumber; args.Script = _project.Scripts.SingleOrDefault(x => x.Name == file); for (int i = 0; i < symbolsMap.Length; i += 4) { args.Symbols.Add(new ScriptBreakPointSymbol() { Name = symbolsMap[i].ToString(), Offset = (int)symbolsMap[i + 1], Length = (int)symbolsMap[i + 2], SymbolObject = symbolsMap[i + 3], }); } DispatcherProvider.Invoke(() => { BreakPointRequest?.Invoke(this, args); }); while (!released) { Thread.Sleep(100); } } } private void EnsureFileLocation(String file) { String dir = Path.GetDirectoryName(file); if (!Directory.Exists(dir)) { Directory.CreateDirectory(dir); } } } }