context: using System;
  using System.Collections.Generic;
  using System.Runtime.Remoting.Messaging;
  using System.Threading;
  using System.Threading.Tasks;
  
  namespace Mono.Debugger.Soft
  {
  	public class InvokeResult {
  		public Value Result { get; set; }
  		//
  		// The value of the receiver after the call for calls to valuetype methods or null.
  		// Only set when using the InvokeOptions.ReturnOutThis flag.
  		// Since protocol version 2.35
  		//
  		public Value OutThis { get; set; }
  		//
  		// The value of the arguments after the call
  		// Only set when using the InvokeOptions.ReturnOutArgs flag.
  		// Since protocol version 2.35
  		//
  		public Value[] OutArgs { get; set; }
  	}
  
  	public class ObjectMirror : Value {
  		TypeMirror type;
  		AppDomainMirror domain;
  	
  		internal ObjectMirror (VirtualMachine vm, long id) : base (vm, id) {
  		}
  	
  		internal ObjectMirror (VirtualMachine vm, long id, TypeMirror type, AppDomainMirror domain) : base (vm, id) {
  			this.type = type;
  			this.domain = domain;
  		}
  
  		void GetInfo () {
  			var info = vm.conn.Object_GetInfo (id);
  			type = vm.GetType (info.type_id);
  			domain = vm.GetDomain (info.domain_id);
  		}
  
  		public TypeMirror Type {
  			get {
  				if (type == null) {
  					if (vm.conn.Version.AtLeast (2, 5))
  						GetInfo ();
  					else
  				 		type = vm.GetType (vm.conn.Object_GetType (id));
  				}
  				return type;
  			}
  		}
  
  		public AppDomainMirror Domain {
  			get {
  				if (domain == null) {
  					if (vm.conn.Version.AtLeast (2, 5))
  						GetInfo ();
  					else
  						domain = vm.GetDomain (vm.conn.Object_GetDomain (id));
  				}
  				return domain;
  			}
  		}
  
  		public bool IsCollected {
  			get {
  				return vm.conn.Object_IsCollected (id);
  			}
  		}
  
  		public Value GetValue (FieldInfoMirror field) {
  			return GetValues (new FieldInfoMirror [] { field }) [0];
  		}
  
  		public Value[] GetValues (IList<FieldInfoMirror> fields) {
  			if (fields == null)
  				throw new ArgumentNullException ("fields");
  			foreach (FieldInfoMirror f in fields) {
  				if (f == null)
  					throw new ArgumentNullException ("field");
  				CheckMirror (f);
  			}
  			long[] ids = new long [fields.Count];
  			for (int i = 0; i < fields.Count; ++i)
  				ids [i] = fields [i].Id;
  			try {
  				return vm.DecodeValues (vm.conn.Object_GetValues (id, ids));
  			} catch (CommandException ex) {
  				if (ex.ErrorCode == ErrorCode.INVALID_FIELDID) {
  					if (fields.Count == 1)
  						throw new ArgumentException (string.Format ("The field '{0}' is not valid for this type.", fields[0].Name));
  					throw new ArgumentException ("One of the fields is not valid for this type.", "fields");
  				} else
  					throw;
  			}
  		}
  
  		public void SetValues (IList<FieldInfoMirror> fields, Value[] values) {
  			if (fields == null)
  				throw new ArgumentNullException ("fields");
  			if (values == null)
  				throw new ArgumentNullException ("values");
  			foreach (FieldInfoMirror f in fields) {
  				if (f == null)
  					throw new ArgumentNullException ("field");
  				CheckMirror (f);
  			}
  			foreach (Value v in values) {
  				if (v == null)
  					throw new ArgumentNullException ("values");
  				CheckMirror (v);
  			}
  			long[] ids = new long [fields.Count];
  			for (int i = 0; i < fields.Count; ++i)
  				ids [i] = fields [i].Id;
  			try {
  				vm.conn.Object_SetValues (id, ids, vm.EncodeValues (values));
  			} catch (CommandException ex) {
  				if (ex.ErrorCode == ErrorCode.INVALID_FIELDID)
  					throw new ArgumentException ("One of the fields is not valid for this type.", "fields");
  				else if (ex.ErrorCode == ErrorCode.INVALID_ARGUMENT)
  					throw new ArgumentException ("One of the values is not valid for its field.", "values");
  				else
  					throw;
  			}
  		}
  
  		public void SetValue (FieldInfoMirror field, Value value) {
  			SetValues (new FieldInfoMirror [] { field }, new Value [] { value });
  		}
  
  		/*
  		 * The current address of the object. It can change during garbage 
  		 * collections. Use a long since the debuggee might have a different 
  		 * pointer size. 
  		 */
  		public long Address {
  			get {
  				return vm.conn.Object_GetAddress (id);
  			}
  		}
  
  		public Value InvokeMethod (ThreadMirror thread, MethodMirror method, IList<Value> arguments) {
  			return InvokeMethod (vm, thread, method, this, arguments, InvokeOptions.None);
  		}
  
  		public Value InvokeMethod (ThreadMirror thread, MethodMirror method, IList<Value> arguments, InvokeOptions options) {
  			return InvokeMethod (vm, thread, method, this, arguments, options);
  		}
  
  		[Obsolete ("Use the overload without the 'vm' argument")]
  		public IAsyncResult BeginInvokeMethod (VirtualMachine vm, ThreadMirror thread, MethodMirror method, IList<Value> arguments, InvokeOptions options, AsyncCallback callback, object state) {
  			return BeginInvokeMethod (vm, thread, method, this, arguments, options, callback, state);
  		}
  
  		public IAsyncResult BeginInvokeMethod (ThreadMirror thread, MethodMirror method, IList<Value> arguments, InvokeOptions options, AsyncCallback callback, object state) {
  			return BeginInvokeMethod (vm, thread, method, this, arguments, options, callback, state);
  		}
  
  		public Value EndInvokeMethod (IAsyncResult asyncResult) {
  			return EndInvokeMethodInternal (asyncResult);
  		}
  
  		public InvokeResult EndInvokeMethodWithResult (IAsyncResult asyncResult) {
  			return  ObjectMirror.EndInvokeMethodInternalWithResult (asyncResult);
  		}
  
  		public Task<Value> InvokeMethodAsync (ThreadMirror thread, MethodMirror method, IList<Value> arguments, InvokeOptions options = InvokeOptions.None) {
  			var tcs = new TaskCompletionSource<Value> ();
  			BeginInvokeMethod (thread, method, arguments, options, iar =>
  					{
  						try {
  							tcs.SetResult (EndInvokeMethod (iar));
  						} catch (OperationCanceledException) {
  							tcs.TrySetCanceled ();
  						} catch (Exception ex) {
  							tcs.TrySetException (ex);
  						}
  					}, null);
  			return tcs.Task;
  		}
  
  		public Task<InvokeResult> InvokeMethodAsyncWithResult (ThreadMirror thread, MethodMirror method, IList<Value> arguments, InvokeOptions options = InvokeOptions.None) {
  			var tcs = new TaskCompletionSource<InvokeResult> ();
  			BeginInvokeMethod (thread, method, arguments, options, iar =>
  					{
  						try {
  							tcs.SetResult (EndInvokeMethodInternalWithResult (iar));
  						} catch (OperationCanceledException) {
  							tcs.TrySetCanceled ();
  						} catch (Exception ex) {
  							tcs.TrySetException (ex);
  						}
  					}, null);
  			return tcs.Task;
  		}
  
  		//
  		// Invoke the members of METHODS one-by-one, calling CALLBACK after each invoke was finished. The IAsyncResult will be marked as completed after all invokes have
  		// finished. The callback will be called with a different IAsyncResult that represents one method invocation.
  		// From protocol version 2.22.
  		//
  		public IAsyncResult BeginInvokeMultiple (ThreadMirror thread, MethodMirror[] methods, IList<IList<Value>> arguments, InvokeOptions options, AsyncCallback callback, object state) {
  			return BeginInvokeMultiple (vm, thread, methods, this, arguments, options, callback, state);
  		}
  
  		public void EndInvokeMultiple (IAsyncResult asyncResult) {
  			EndInvokeMultipleInternal (asyncResult);
  		}
  
  		/*
  		 * Common implementation for invokes
  		 */
  
  		class InvokeAsyncResult : IInvokeAsyncResult {
  
  			public object AsyncState {
  				get; set;
  			}
  
  			public WaitHandle AsyncWaitHandle {
  				get; set;
  			}
  
  			public bool CompletedSynchronously {
  				get {
  					return false;
  				}
  			}
  
  			public bool IsCompleted {
  				get; set;
  			}
  
  			public AsyncCallback Callback {
  				get; set;
  			}
  
  			public ErrorCode ErrorCode {
  				get; set;
  			}
  
  			public VirtualMachine VM {
  				get; set;
  			}
  
  			public ThreadMirror Thread {
  				get; set;
  			}
  
  			public ValueImpl Value {
  				get; set;
  			}
  
  			public ValueImpl OutThis {
  				get; set;
  			}
  
  			public ValueImpl[] OutArgs {
  				get; set;
  			}
  
  			public ValueImpl Exception {
  				get; set;
  			}
  
  			public int ID {
  				get; set;
  			}
  
  			public bool IsMultiple {
  				get; set;
  			}
  			   
  			public int NumPending;
  
  			public void Abort ()
  			{
  				if (ID == 0) // Ooops
  					return;
  
  				ObjectMirror.AbortInvoke (VM, Thread, ID);
  			}
  		}
  
  		internal static IInvokeAsyncResult BeginInvokeMethod (VirtualMachine vm, ThreadMirror thread, MethodMirror method, Value this_obj, IList<Value> arguments, InvokeOptions options, AsyncCallback callback, object state) {
  			if (thread == null)
  				throw new ArgumentNullException ("thread");
  			if (method == null)
  				throw new ArgumentNullException ("method");
  			if (arguments == null)
  				arguments = new Value [0];
  
  			InvokeFlags f = InvokeFlags.NONE;
  
  			if ((options & InvokeOptions.DisableBreakpoints) != 0)
  				f |= InvokeFlags.DISABLE_BREAKPOINTS;
  			if ((options & InvokeOptions.SingleThreaded) != 0)
  				f |= InvokeFlags.SINGLE_THREADED;
  			if ((options & InvokeOptions.ReturnOutThis) != 0)
  				f |= InvokeFlags.OUT_THIS;
  			if ((options & InvokeOptions.ReturnOutArgs) != 0)
  				f |= InvokeFlags.OUT_ARGS;
  			if ((options & InvokeOptions.Virtual) != 0)
  				f |= InvokeFlags.VIRTUAL;
  
  			InvokeAsyncResult r = new InvokeAsyncResult { AsyncState = state, AsyncWaitHandle = new ManualResetEvent (false), VM = vm, Thread = thread, Callback = callback };
  			thread.InvalidateFrames ();
  			r.ID = vm.conn.VM_BeginInvokeMethod (thread.Id, method.Id, this_obj != null ? vm.EncodeValue (this_obj) : vm.EncodeValue (vm.CreateValue (null)), vm.EncodeValues (arguments), f, InvokeCB, r);
  
  			return r;
  		}
  
  		// This is called when the result of an invoke is received
  		static void InvokeCB (ValueImpl v, ValueImpl exc, ValueImpl out_this, ValueImpl[] out_args, ErrorCode error, object state) {
  			InvokeAsyncResult r = (InvokeAsyncResult)state;
  
  			if (error != 0) {
  				r.ErrorCode = error;
  			} else {
  				r.Value = v;
  				r.Exception = exc;
  			}
  
  			r.OutThis = out_this;
  			r.OutArgs = out_args;
  
  			r.IsCompleted = true;
  			((ManualResetEvent)r.AsyncWaitHandle).Set ();
  
  			if (r.Callback != null)
  				r.Callback.BeginInvoke (r, null, null);
  		}
  
  	    internal static InvokeResult EndInvokeMethodInternalWithResult (IAsyncResult asyncResult) {
  			if (asyncResult == null)
  
gt: 				throw new ArgumentNullException ("asyncResult");