context: using System;
  using System.IO;
  using System.Threading;
  using System.Net;
  using System.Diagnostics;
  using System.Collections;
  using System.Collections.Generic;
  using Mono.Cecil.Metadata;
  
  namespace Mono.Debugger.Soft
  {
  	public class VirtualMachine : Mirror
  	{
  		Queue queue;
  		object queue_monitor;
  		object startup_monitor;
  		AppDomainMirror root_domain;
  		Dictionary<int, EventRequest> requests;
  		ITargetProcess process;
  
  		internal Connection conn;
  
  		VersionInfo version;
  
  		internal VirtualMachine (ITargetProcess process, Connection conn) : base () {
  			SetVirtualMachine (this);
  			queue = new Queue ();
  			queue_monitor = new Object ();
  			startup_monitor = new Object ();
  			requests = new Dictionary <int, EventRequest> ();
  			this.conn = conn;
  			this.process = process;
  			conn.ErrorHandler += ErrorHandler;
  		}
  
  		// The standard output of the process is available normally through Process
  		public StreamReader StandardOutput { get; set; }
  		public StreamReader StandardError { get; set; }
  
  		
  		public Process Process {
  			get {
  				ProcessWrapper pw = process as ProcessWrapper;
  				if (pw == null)
  				    throw new InvalidOperationException ("Process instance not available");
  				return pw.Process;
  			}
  		}
  
  		public ITargetProcess TargetProcess {
  			get {
  				return process;
  			}
  		}
  
  		public AppDomainMirror RootDomain {
  			get {
  				return root_domain;
  			}
  	    }
  
  		public EndPoint EndPoint {
  			get {
  				var tcpConn = conn as TcpConnection;
  				if (tcpConn != null)
  					return tcpConn.EndPoint;
  				return null;
  			}
  		}
  
  		public VersionInfo Version {
  			get {
  				return version;
  			}
  		}
  
  		EventSet current_es;
  		int current_es_index;
  
  		/*
  		 * It is impossible to determine when to resume when using this method, since
  		 * the debuggee is suspended only once per event-set, not event.
  		 */
  		[Obsolete ("Use GetNextEventSet () instead")]
  		public Event GetNextEvent () {
  			lock (queue_monitor) {
  				if (current_es == null || current_es_index == current_es.Events.Length) {
  					if (queue.Count == 0)
  						Monitor.Wait (queue_monitor);
  					current_es = (EventSet)queue.Dequeue ();
  					current_es_index = 0;
  				}
  				return current_es.Events [current_es_index ++];
  			}
  		}
  
  		public Event GetNextEvent (int timeout) {
  			throw new NotImplementedException ();
  		}
  
  		public EventSet GetNextEventSet () {
  			lock (queue_monitor) {
  				if (queue.Count == 0)
  					Monitor.Wait (queue_monitor);
  
  				current_es = null;
  				current_es_index = 0;
  
  				return (EventSet)queue.Dequeue ();
  			}
  		}
  
  		[Obsolete ("Use GetNextEventSet () instead")]
  		public T GetNextEvent<T> () where T : Event {
  			return GetNextEvent () as T;
  		}
  
  		public void Suspend () {
  			conn.VM_Suspend ();
  	    }
  
  		public void Resume () {
  			try {
  				InvalidateThreadAndFrameCaches ();
  				conn.VM_Resume ();
  			} catch (CommandException ex) {
  				if (ex.ErrorCode == ErrorCode.NOT_SUSPENDED)
  					throw new VMNotSuspendedException ();
  
  				throw;
  			}
  	    }
  
  		public void Exit (int exitCode) {
  			conn.VM_Exit (exitCode);
  		}
  
  		public void Detach () {
  			conn.VM_Dispose ();
  			conn.Close ();
  			notify_vm_event (EventType.VMDisconnect, SuspendPolicy.None, 0, 0, null, 0);
  		}
  
  		[Obsolete ("This method was poorly named; use the Detach() method instead")]
  		public void Dispose ()
  		{
  			Detach ();
  		}
  
  		public void ForceDisconnect ()
  		{
  			conn.ForceDisconnect ();
  		}
  
  		HashSet<ThreadMirror> threadsToInvalidate = new HashSet<ThreadMirror> ();
  		ThreadMirror[] threadCache;
  		object threadCacheLocker = new object ();
  
  		void InvalidateThreadAndFrameCaches () {
  			lock (threadsToInvalidate) {
  				foreach (var thread in threadsToInvalidate)
  					thread.InvalidateFrames ();
  				threadsToInvalidate.Clear ();
  			}
  			threadCache = null;
  		}
  
  		internal void InvalidateThreadCache () {
  			threadCache = null;
  		}
  
  		internal void AddThreadToInvalidateList (ThreadMirror threadMirror)
  		{
  			lock (threadsToInvalidate) {
  				threadsToInvalidate.Add (threadMirror);
  			}
  		}
  
  		public IList<ThreadMirror> GetThreads () {
  			var threads = threadCache;
  			if (threads == null) {
  				long[] ids = null;
  				var fetchingEvent = new ManualResetEvent (false);
  				vm.conn.VM_GetThreads ((threadsIds) => {
  					ids = threadsIds;
  					threadCache = threads = new ThreadMirror [threadsIds.Length];
  					fetchingEvent.Set ();
  				});
  				if (WaitHandle.WaitAny (new []{ vm.conn.DisconnectedEvent, fetchingEvent }) == 0) {
  					throw new VMDisconnectedException ();
  				}
  				for (int i = 0; i < ids.Length; ++i)
  					threads [i] = GetThread (ids [i]);
  				//Uncomment lines below if you want to re-fetch threads if new threads were started/stopped while
  				//featching threads... This is probably more correct but might cause deadlock of this method if runtime
  				//is starting/stopping threads nonstop, need way to prevent this(counting number of recursions?)
  				//possiblity before uncommenting
  				//if (threadCache != threads) {//While fetching threads threadCache was invalidated(thread was created/destoyed)
  				//	return GetThreads ();
  				//}
  				return threads;
  			} else {
  				return threads;
  			}
  		}
  
  		// Same as the mirrorOf methods in JDI
  		public PrimitiveValue CreateValue (object value) {
  			if (value == null)
  				return new PrimitiveValue (vm, null);
  
  			if (!value.GetType ().IsPrimitive)
  				throw new ArgumentException ("value must be of a primitive type instead of '" + value.GetType () + "'", "value");
  
  			return new PrimitiveValue (vm, value);
  		}
  
  		public EnumMirror CreateEnumMirror (TypeMirror type, PrimitiveValue value) {
  			return new EnumMirror (this, type, value);
  		}
  
  		//
  		// Enable send and receive timeouts on the connection and send a keepalive event
  		// every 'keepalive_interval' milliseconds.
  		//
  
  		public void SetSocketTimeouts (int send_timeout, int receive_timeout, int keepalive_interval)
  		{
  			conn.SetSocketTimeouts (send_timeout, receive_timeout, keepalive_interval);
  		}
  
  		//
  		// Methods to create event request objects
  		//
  		public BreakpointEventRequest CreateBreakpointRequest (MethodMirror method, long il_offset) {
  			return new BreakpointEventRequest (this, method, il_offset);
  		}
  
  		public BreakpointEventRequest CreateBreakpointRequest (Location loc) {
  			if (loc == null)
  				throw new ArgumentNullException ("loc");
  			CheckMirror (loc);
  			return new BreakpointEventRequest (this, loc.Method, loc.ILOffset);
  		}
  
  		public StepEventRequest CreateStepRequest (ThreadMirror thread) {
  			return new StepEventRequest (this, thread);
  		}
  
  		public MethodEntryEventRequest CreateMethodEntryRequest () {
  			return new MethodEntryEventRequest (this);
  		}
  
  		public MethodExitEventRequest CreateMethodExitRequest () {
  			return new MethodExitEventRequest (this);
  		}
  
  		public ExceptionEventRequest CreateExceptionRequest (TypeMirror exc_type) {
  			return new ExceptionEventRequest (this, exc_type, true, true);
  		}
  
  		public ExceptionEventRequest CreateExceptionRequest (TypeMirror exc_type, bool caught, bool uncaught) {
  			return new ExceptionEventRequest (this, exc_type, caught, uncaught);
  		}
  
  		public AssemblyLoadEventRequest CreateAssemblyLoadRequest () {
  			return new AssemblyLoadEventRequest (this);
  		}
  
  		public TypeLoadEventRequest CreateTypeLoadRequest () {
  			return new TypeLoadEventRequest (this);
  		}
  
  		public void EnableEvents (params EventType[] events) {
  			EnableEvents (events, SuspendPolicy.All);
  		}
  
  		public void EnableEvents (EventType[] events, SuspendPolicy suspendPolicy) {
  			foreach (EventType etype in events) {
  				if (etype == EventType.Breakpoint)
  					throw new ArgumentException ("Breakpoint events cannot be requested using EnableEvents", "events");
  				conn.EnableEvent (etype, suspendPolicy, null);
  			}
  		}
  
  		public BreakpointEventRequest SetBreakpoint (MethodMirror method, long il_offset) {
  			BreakpointEventRequest req = CreateBreakpointRequest (method, il_offset);
  
  			req.Enable ();
  
  			return req;
  		}
  
  		public void ClearAllBreakpoints () {
  			conn.ClearAllBreakpoints ();
  		}
  		
  		public void Disconnect () {
  			conn.Close ();
  		}
  
  		//
  		// Return a list of TypeMirror objects for all loaded types which reference the
  		// source file FNAME. Might return false positives.
  		// Since protocol version 2.7.
  		//
  		public IList<TypeMirror> GetTypesForSourceFile (string fname, bool ignoreCase) {
  			long[] ids = conn.VM_GetTypesForSourceFile (fname, ignoreCase);
  			var res = new TypeMirror [ids.Length];
  			for (int i = 0; i < ids.Length; ++i)
  				res [i] = GetType (ids [i]);
  			return res;
  		}
  
  		//
  		// Return a list of TypeMirror objects for all loaded types named 'NAME'.
  		// NAME should be in the the same for as with Assembly.GetType ().
  		// Since protocol version 2.9.
  		//
  		public IList<TypeMirror> GetTypes (string name, bool ignoreCase) {
  			long[] ids = conn.VM_GetTypes (name, ignoreCase);
  			var res = new TypeMirror [ids.Length];
  			for (int i = 0; i < ids.Length; ++i)
  				res [i] = GetType (ids [i]);
  			return res;
  		}
  		
  		internal void queue_event_set (EventSet es) {
  			lock (queue_monitor) {
  				queue.Enqueue (es);
  				Monitor.Pulse (queue_monitor);
  			}
  		}
  
  		internal void ErrorHandler (object sender, ErrorHandlerEventArgs args) {
  			switch (args.ErrorCode) {
  			case ErrorCode.INVALID_OBJECT:
  				throw new ObjectCollectedException ();
  			case ErrorCode.INVALID_FRAMEID:
  				throw new InvalidStackFrameException ();
  			case ErrorCode.NOT_SUSPENDED:
  				throw new VMNotSuspendedException ();
  			case ErrorCode.NOT_IMPLEMENTED:
  				throw new NotSupportedException ("This request is not supported by the protocol version implemented by the debuggee.");
  			case ErrorCode.ABSENT_INFORMATION:
  				throw new AbsentInformationException ();
  			case ErrorCode.NO_SEQ_POINT_AT_IL_OFFSET:
  				throw new ArgumentException ("Cannot set breakpoint on the specified IL offset.");
  			default:
  				throw new CommandException (args.ErrorCode);
  			}
  		}
  
  		/* Wait for the debuggee to start up and connect to it */
  		internal void connect () {
  			conn.Connect ();
  
  			// Test the connection
  			version = conn.Version;
  			if (version.MajorVersion != Connection.MAJOR_VERSION)
  				throw new NotSupportedException (String.Format ("The debuggee implements protocol version {0}.{1}, while {2}.{3} is required.", version.MajorVersion, version.MinorVersion, Connection.MAJOR_VERSION, Connection.MINOR_VERSION));
  
  			long root_domain_id = conn.RootDomain;
  			root_domain = GetDomain (root_domain_id);
  		}
  
  		internal void notify_vm_event (EventType evtype, SuspendPolicy spolicy, int req_id, long thread_id, string vm_uri, int exit_code) {
  			//Console.WriteLine ("Event: " + evtype + "(" + vm_uri + ")");
  
  			switch (evtype) {
  			case EventType.VMStart:
  				/* Notify the main thread that the debuggee started up */
  				lock (startup_monitor) {
  					Monitor.Pulse (startup_monitor);
  				}
  				queue_event_set (new EventSet (this, spolicy, new Event[] { new VMStartEvent (vm, req_id, thread_id) }));
  				break;
  			case EventType.VMDeath:
  				queue_event_set (new EventSet (this, spolicy, new Event[] { new VMDeathEvent (vm, req_id, exit_code) }));
  				break;
  			case EventType.VMDisconnect:
  				queue_event_set (new EventSet (this, spolicy, new Event[] { new VMDisconnectEvent (vm, req_id) }));
  				break;
  			default:
  				throw new Exception ();
  			}
  		}
  
  		//
  		// Methods to create instances of mirror objects
  		//
  
  		/*
  		class MirrorCache<T> {
  			static Dictionary <long, T> mirrors;
  			static object mirror_lock = new object ();
  
  			internal static T GetMirror (VirtualMachine vm, long id) {
  				lock (mirror_lock) {
  				if (mirrors == null)
  					mirrors = new Dictionary <long, T> ();
  				T obj;
  				if (!mirrors.TryGetValue (id, out obj)) {
  					obj = CreateMirror (vm, id);
  					mirrors [id] = obj;
  				}
  				return obj;
  				}
  			}
  
  			internal static T CreateMirror (VirtualMachine vm, long id) {
  			}
  		}
  		*/
  
  		// FIXME: When to remove items from the cache ?
  
  		Dictionary <long, MethodMirror> methods;
  		object methods_lock = new object ();
  
  		internal MethodMirror GetMethod (long id) {
  			lock (methods_lock) {
  				if (methods == null)
  					methods = new Dictionary <long, MethodMirror> ();
  				MethodMirror obj;
  				if (id == 0)
  					return null;
  				if (!methods.TryGetValue (id, out obj)) {
  					obj = new MethodMirror (this, id);
  					methods [id] = obj;
  				}
  				return obj;
  			}
  	    }
  
  		Dictionary <long, AssemblyMirror> assemblies;
  		object assemblies_lock = new object ();
  
  		internal AssemblyMirror GetAssembly (long id) {
  			lock (assemblies_lock) {
  				if (assemblies == null)
  					assemblies = new Dictionary <long, AssemblyMirror> ();
  				AssemblyMirror obj;
  				if (id == 0)
  					return null;
  				if (!assemblies.TryGetValue (id, out obj)) {
  					obj = new AssemblyMirror (this, id);
  					assemblies [id] = obj;
  				}
  				return obj;
  			}
  	    }
  
  		Dictionary <long, ModuleMirror> modules;
  		object modules_lock = new object ();
  
  		internal ModuleMirror GetModule (long id) {
  			lock (modules_lock) {
  				if (modules == null)
  					modules = new Dictionary <long, ModuleMirror> ();
  				ModuleMirror obj;
  				if (id == 0)
  					return null;
  				if (!modules.TryGetValue (id, out obj)) {
  					obj = new ModuleMirror (this, id);
  					modules [id] = obj;
  				}
  				return obj;
  			}
  	    }
  
  		Dictionary <long, AppDomainMirror> domains;
  		object domains_lock = new object ();
  
  		internal AppDomainMirror GetDomain (long id) {
  			lock (domains_lock) {
  				if (domains == null)
  					domains = new Dictionary <long, AppDomainMirror> ();
  				AppDomainMirror obj;
  				if (id == 0)
  					return null;
  				if (!domains.TryGetValue (id, out obj)) {
  					obj = new AppDomainMirror (this, id);
  					domains [id] = obj;
  				}
  				return obj;
  			}
  	    }
  
  		internal void InvalidateAssemblyCaches () {
  			lock (domains_lock) {
  				foreach (var d in domains.Values)
  					d.InvalidateAssembliesCache ();
  			}
  		}
  
  		Dictionary <long, TypeMirror> types;
  		object types_lock = new object ();
  
  		internal TypeMirror GetType (long id) {
  			lock (types_lock) {
  				if (types == null)
  					types = new Dictionary <long, TypeMirror> ();
  				TypeMirror obj;
  				if (id == 0)
  					return null;
  				if (!types.TryGetValue (id, out obj)) {
  					obj = new TypeMirror (this, id);
  					types [id] = obj;
  				}
  				return obj;
  			}
  	    }
  
  		internal TypeMirror[] GetTypes (long[] ids) {
  			var res = new TypeMirror [ids.Length];
  			for (int i = 0; i < ids.Length; ++i)
  				res [i] = GetType (ids [i]);
  			return res;
  		}
  
  		Dictionary <long, ObjectMirror> objects;
  		object objects_lock = new object ();
  
  		internal T GetObject<T> (long id, long domain_id, long type_id) where T : ObjectMirror {
  			lock (objects_lock) {
  				if (objects == null)
  					objects = new Dictionary <long, ObjectMirror> ();
  				ObjectMirror obj;
  				if (!objects.TryGetValue (id, out obj)) {
  					/*
  					 * Obtain the domain/type of the object to determine the type of
  					 * object we need to create.
  					 */
  					if (domain_id == 0 || type_id == 0) {
  						if (conn.Version.AtLeast (2, 5)) {
  							var info = conn.Object_GetInfo (id);
  							domain_id = info.domain_id;
  							type_id = info.type_id;
  						} else {
  							if (domain_id == 0)
  								domain_id = conn.Object_GetDomain (id);
  							if (type_id == 0)
  								type_id = conn.Object_GetType (id);
  						}
  					}
  					AppDomainMirror d = GetDomain (domain_id);
  					TypeMirror t = GetType (type_id);
  
  					if (t.Assembly == d.Corlib && t.Namespace == "System.Threading" && t.Name == "Thread")
  						obj = new ThreadMirror (this, id, t, d);
  					else if (t.Assembly == d.Corlib && t.Namespace == "System" && t.Name == "String")
  						obj = new StringMirror (this, id, t, d);
  					else if (typeof (T) == typeof (ArrayMirror))
  
gt: 						obj = new ArrayMirror (this, id, t, d);