context: // 
  // AutoTestServer.cs
  //  
  // Author:
  //       Lluis Sanchez Gual <lluis@novell.com>
  // 
  // Copyright (c) 2010 Novell, Inc (http://www.novell.com)
  // 
  // Permission is hereby granted, free of charge, to any person obtaining a copy
  // of this software and associated documentation files (the "Software"), to deal
  // in the Software without restriction, including without limitation the rights
  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  // copies of the Software, and to permit persons to whom the Software is
  // furnished to do so, subject to the following conditions:
  // 
  // The above copyright notice and this permission notice shall be included in
  // all copies or substantial portions of the Software.
  // 
  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  // THE SOFTWARE.
  
  using System;
  using System.Diagnostics;
  using System.IO;
  using System.Runtime.InteropServices;
  using System.Linq;
  using System.Reflection;
  using System.Threading;
  using System.Text;
  using System.Collections.Generic;
  using MonoDevelop.Core.Instrumentation;
  using MonoDevelop.Ide;
  using MonoDevelop.Ide.Tasks;
  using MonoDevelop.Components.Commands;
  using MonoDevelop.Core;
  
  using System.Xml;
  
  namespace MonoDevelop.Components.AutoTest
  {
  	public class AutoTestSession: MarshalByRefObject
  	{		
  		[System.Runtime.InteropServices.DllImport ("/System/Library/Frameworks/QuartzCore.framework/QuartzCore")]
  		static extern IntPtr CGDisplayCreateImage (int displayID);
  
  		[System.Runtime.InteropServices.DllImport("/System/Library/Frameworks/ApplicationServices.framework/Versions/Current/ApplicationServices", EntryPoint="CGMainDisplayID")]
  		internal static extern int MainDisplayID();
  
  		readonly ManualResetEvent syncEvent = new ManualResetEvent (false);
  		public readonly AutoTestSessionDebug SessionDebug = new AutoTestSessionDebug ();
  		public IAutoTestSessionDebug<MarshalByRefObject> DebugObject { 
  			get { return SessionDebug.DebugObject; }
  			set { SessionDebug.DebugObject = value; }
  		}
  
  		public AutoTestSession ()
  		{
  		}
  		
  		public override object InitializeLifetimeService ()
  		{
  			return null;
  		}
  
  		[Serializable]
  		public struct MemoryStats {
  			public long PrivateMemory;
  			public long VirtualMemory;
  			public long WorkingSet;
  			public long PeakVirtualMemory;
  			public long PagedSystemMemory;
  			public long PagedMemory;
  			public long NonPagedSystemMemory;
  		};
  
  		public MemoryStats GetMemoryStats ()
  		{
  			MemoryStats stats;
  			using (Process proc = Process.GetCurrentProcess ()) {
  				stats = new MemoryStats {
  					PrivateMemory = proc.PrivateMemorySize64,
  					VirtualMemory = proc.VirtualMemorySize64,
  					WorkingSet = proc.WorkingSet64,
  					PeakVirtualMemory = proc.PeakVirtualMemorySize64,
  					PagedSystemMemory = proc.PagedSystemMemorySize64,
  					PagedMemory = proc.PagedMemorySize64,
  					NonPagedSystemMemory = proc.NonpagedSystemMemorySize64
  				};
  				return stats;
  			}
  		}
  
  		public string[] GetCounterStats ()
  		{
  			return Counters.CounterReport ();
  		}
  
  		public void ExecuteCommand (object cmd, object dataItem = null, CommandSource source = CommandSource.Unknown)
  		{
  			Gtk.Application.Invoke ((o, args) => {
  				AutoTestService.CommandManager.DispatchCommand (cmd, dataItem, null, source);
  			});
  		}
  		
  		object Sync (Func<object> del, bool safe)
  		{
  			object res = null;
  			Exception error = null;
  
  			if (Runtime.IsMainThread) {
  				res = del ();
  				return safe ? SafeObject (res) : res;
  			}
  
  			syncEvent.Reset ();
  			Gtk.Application.Invoke ((o, args) => {
  				try {
  					res = del ();
  				} catch (Exception ex) {
  					error = ex;
  				} finally {
  					syncEvent.Set ();
  				}
  			});
  			if (!syncEvent.WaitOne (20000))
  				throw new TimeoutException ("Timeout while executing synchronized call");
  			if (error != null)
  				throw error;
  			return safe ? SafeObject (res) : res;
  		}
  
  		public object Sync (Func<object> del)
  		{
  			return Sync (del, true);
  		}
  
  		// The difference between Sync and UnsafeSync is that UnsafeSync will return objects
  		// that may not be safe to be serialized across the remoting channel.
  		// But that is ok, so long as we know the places where it is being called from will
  		// not be trying to send the object over the remoting channel.
  		public object UnsafeSync (Func<object> del)
  		{
  			return Sync (del, false);
  		}
  
  		public void ExitApp ()
  		{
  			Sync (delegate {
  				IdeApp.Exit ().ContinueWith ((arg) => {
  					if (arg.IsFaulted)
  						Console.WriteLine (arg.Exception);
  				});
  				return true;
  			});
  		}
  
  		public object GlobalInvoke (string name, object[] args)
  		{
  			return Sync (delegate {
  				int i = name.LastIndexOf ('.');
  				if (i == -1)
  					throw new Exception ("Member name not specified");
  				object ob = GetGlobalObject (name.Substring (0, i));
  				if (ob == null)
  					throw new Exception ("Object not found");
  				var type = ob as Type;
  				if (type != null)
  					return Invoke (null, type, name.Substring (i + 1), args);
  				return Invoke (ob, ob.GetType (), name.Substring (i + 1), args);
  			});
  		}
  		
  		public object GetGlobalValue (string name)
  		{
  			return Sync (delegate {
  				return GetGlobalObject (name);
  			});
  		}
  
  		public void TakeScreenshot (string screenshotPath)
  		{
  			#if MAC
  			Runtime.RunInMainThread (delegate {
  				try {
  					IntPtr handle = CGDisplayCreateImage (MainDisplayID ());
  					CoreGraphics.CGImage screenshot = ObjCRuntime.Runtime.GetINativeObject <CoreGraphics.CGImage> (handle, true);
  					AppKit.NSBitmapImageRep imgRep =  new AppKit.NSBitmapImageRep (screenshot);
  					var imageData = imgRep.RepresentationUsingTypeProperties (AppKit.NSBitmapImageFileType.Png);
  					imageData.Save (screenshotPath, true);
  				} catch (Exception e) {
  					Console.WriteLine (e);
  					throw;
  				}
  			});
  			#else
  			Sync (delegate {
  				try {
  					using (var bmp = new System.Drawing.Bitmap (System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width,
  						System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height)) {
  						using (var g = System.Drawing.Graphics.FromImage(bmp))
  						{
  							g.CopyFromScreen(System.Windows.Forms.Screen.PrimaryScreen.Bounds.X,
  								System.Windows.Forms.Screen.PrimaryScreen.Bounds.Y,
  								0, 0,
  								bmp.Size,
  								System.Drawing.CopyPixelOperation.SourceCopy);
  						}
  						bmp.Save(screenshotPath);
  					}
  					return null;
  				} catch (Exception e) {
  					Console.WriteLine (e);
  					throw;
  				}
  			});
  			#endif
  		}
  		
  		public void SetGlobalValue (string name, object value)
  		{
  			Sync (delegate {
  				int i = name.LastIndexOf ('.');
  				if (i == -1)
  					throw new Exception ("Member name not specified");
  				object ob = GetGlobalObject (name.Substring (0, i));
  				if (ob == null)
  					throw new Exception ("Object not found");
  				var type = ob as Type;
  				if (type != null)
  					SetValue (null, type, name.Substring (i + 1), value);
  				else
  					SetValue (ob, ob.GetType (), name.Substring (i+1), value);
  				return null;
  			});
  		}
  			
  		// FIXME: This shouldn't be here.
  		public int ErrorCount (TaskSeverity severity)
  		{
  			return TaskService.Errors.Count (x => x.Severity == severity);
  		}
  
  		public List<TaskListEntryDTO> GetErrors (TaskSeverity severity)
  		{
  			return TaskService.Errors.Where (x => x.Severity == severity).Select (x => new TaskListEntryDTO () {
  				Line = x.Line,
  				Description = x.Description,
  				File = x.FileName.FileName,
  				Path = x.FileName.FullPath,
  				Project = x.WorkspaceObject?.Name
  			}).ToList ();
  		}
  
  		object SafeObject (object ob)
  		{
  			if (ob == null)
  				return null;
  			if (ob.GetType ().IsPrimitive || ob.GetType ().IsSerializable)
  				return ob;
  			if (ob is string)
  				return ob;
  			return null;
  		}
  		
  		object Invoke (object target, Type type, string methodName, object[] args)
  		{
  			BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
  			flags |= BindingFlags.InvokeMethod;
  			return type.InvokeMember (methodName, flags, null, target, args);
  		}
  		
  		object GetValue (object target, Type type, string name)
  		{
  			int i = name.IndexOf ('.');
  			string remaining = null;
  			if (i != -1) {
  				remaining = name.Substring (i+1);
  				name = name.Substring (0, i);
  			}
  			BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
  			flags |= BindingFlags.GetField | BindingFlags.GetProperty;
  			object res = type.InvokeMember (name, flags, null, target, null);
  			
  			if (remaining == null)
  				return res;
  			else if (res == null)
  				return null;
  			else
  				return GetValue (res, res.GetType (), remaining);
  		}
  		
  		void SetValue (object target, Type type, string name, object value)
  		{
  			BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
  			flags |= BindingFlags.SetField | BindingFlags.SetProperty;
  			type.InvokeMember (name, flags, null, target, new object[] { value });
  		}
  			
  		object GetGlobalObject (string name)
  		{
  			int i = 0;
  			Type type = null;
  			var asms = AppDomain.CurrentDomain.GetAssemblies ();
  			do {
  				i = name.IndexOf ('.', i);
  				if (i == -1)
  					i = name.Length;
  				string cname = name.Substring (0, i);
  				foreach (var a in asms) {
  					type = a.GetType (cname);
  					if (type != null)
  						break;
  				}
  				i++;
  			}
  			while (type == null && i < name.Length);
  			
  			if (type == null)
  				throw new Exception ("Object '" + name + "' not found");				
  			
  			if (i >= name.Length)
  				return type;
  			
  			return GetValue (null, type, name.Substring (i));
  		}
  
  		public AppQuery CreateNewQuery ()
  		{
  			AppQuery query = new AppQuery ();
  			query.SessionDebug = SessionDebug;
  
  			return query;
  		}
  
  		public void ExecuteOnIdle (Action idleFunc, bool wait = true, int timeout = 20000)
  		{
  			if (Runtime.IsMainThread) {
  				idleFunc ();
  				return;
  			}
  
  			if (wait == false) {
  				GLib.Idle.Add (() => {
  					idleFunc ();
  					return false;
  				});
  
  				return;
  			}
  				
  			syncEvent.Reset ();
  			GLib.Idle.Add (() => {
  				idleFunc ();
  				syncEvent.Set ();
  				return false;
  			});
  
  			if (!syncEvent.WaitOne (timeout)) {
  				throw new TimeoutException ("Timeout while executing ExecuteOnIdleAndWait");
  			}
  		}
  
  		// Executes the query outside of a syncEvent wait so it is safe to call from
  		// inside an ExecuteOnIdleAndWait
  		internal AppResult[] ExecuteQueryNoWait (AppQuery query)
  		{
  			AppResult[] resultSet = query.Execute ();
  			Sync (() => {
  				DispatchService.RunPendingEvents ();
  				return true;
  			});
  
  			return resultSet;
  		}
  
  		public AppResult[] ExecuteQuery (AppQuery query, int timeout = 20000)
  		{
  			AppResult[] resultSet = null;
  
  			try {
  				ExecuteOnIdle (() => {
  					resultSet = ExecuteQueryNoWait (query);
  				});
  			} catch (TimeoutException e) {
  				throw new TimeoutException (string.Format ("Timeout while executing ExecuteQuery: {0}", query), e);
  			}
  			return resultSet;
  		}
  
  		public AppResult[] WaitForElement (AppQuery query, int timeout)
  		{
  			const int pollTime = 200;
  			syncEvent.Reset ();
  			AppResult[] resultSet = null;
  
  			GLib.Timeout.Add ((uint)pollTime, () => {
  				resultSet = ExecuteQueryNoWait (query);
  
  				if (resultSet.Length > 0) {
  					syncEvent.Set ();
  					return false;
  				}
  
  				timeout -= pollTime;
  				return timeout > 0;
  			});
  
  			if (!syncEvent.WaitOne (timeout)) {
  				throw new TimeoutException (String.Format ("Timeout while executing WaitForElement: {0}", query));
  			}
  
  			return resultSet;
  		}
  
  		public void WaitForNoElement (AppQuery query, int timeout)
  		{
  			const int pollTime = 100;
  			syncEvent.Reset ();
  			AppResult[] resultSet = null;
  
  			GLib.Timeout.Add ((uint)pollTime, () => {
  				resultSet = ExecuteQueryNoWait (query);
  				if (resultSet.Length == 0) {
  					syncEvent.Set ();
  					return false;
  				}
  
  				timeout -= pollTime;
  				return timeout > 0;
  			});
  
  			if (!syncEvent.WaitOne (timeout)) {
  				throw new TimeoutException (String.Format ("Timeout while executing WaitForNoElement: {0}", query));
  			}
  		}
  
  		[Serializable]
  		public struct TimerCounterContext {
  			public string CounterName;
  			public TimeSpan TotalTime;
  		};
  
  		[Serializable]
  		public struct CounterContext {
  			public string CounterName;
  			public int InitialCount;
  		}
  
  		internal Counter GetCounterByIDOrName (string idOrName)
  		{
  			Counter c = InstrumentationService.GetCounterByID (idOrName);
  			return c ?? InstrumentationService.GetCounter (idOrName);
  		}
  
  		public TimerCounterContext CreateNewTimerContext (string counterName)
  		{
  			TimerCounter tc = GetCounterByIDOrName (counterName) as TimerCounter;
  			if (tc == null) {
  				throw new Exception ("Unknown timer counter " + counterName);
  			}
  
  			TimerCounterContext context = new TimerCounterContext {
  				CounterName = counterName,
  				TotalTime = tc.TotalTime
  			};
  
  			return context;
  		}
  
  		public void WaitForTimerContext (TimerCounterContext context, int timeout = 20000, int pollStep = 200)
  		{
  			TimerCounter tc = GetCounterByIDOrName (context.CounterName) as TimerCounter;
  			if (tc == null) {
  				throw new Exception ("Unknown timer counter " + context.CounterName);
  			}
  
  			do {
  				if (tc.TotalTime > context.TotalTime) {
  					return;
  				}
  
  				timeout -= pollStep;
  				Thread.Sleep (pollStep);
  			} while (timeout > 0);
  
  			throw new TimeoutException ("Timed out waiting for event");
  		}
  
  		public CounterContext CreateNewCounterContext (string counterName)
  		{
  			var counter = GetCounterByIDOrName (counterName);
  			if (counter == null) {
  				throw new Exception ($"Unknown counter {counterName}");
  			}
  
  			var context = new CounterContext {
  				CounterName = counterName,
  				InitialCount = counter.Count
  			};
  
  			return context;
  		}
  
  		public void WaitForCounterToChange (CounterContext context, int timeout = 20000, int pollStep = 200)
  		{
  			var counter = GetCounterByIDOrName (context.CounterName);
  			if (counter == null) {
  				throw new Exception ($"Unknown counter {context.CounterName}");
  			}
  
  			do {
  				if (counter.Count != context.InitialCount) {
  					return;
  				}
  
  				timeout -= pollStep;
  				Thread.Sleep (pollStep);
  			} while (timeout > 0);
  
  
gt: 			throw new TimeoutException ("Timed out waiting for counter");