using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using Mono.Cecil.Metadata;

namespace Mono.Debugger.Soft
{
	public class VersionInfo {
		public string VMVersion {
			get; set;
		}

		public int MajorVersion {
			get; set;
		}

		public int MinorVersion {
			get; set;
		}

		/*
		 * Check that this version is at least major:minor
		 */
		public bool AtLeast (int major, int minor) {
			if ((MajorVersion > major) || ((MajorVersion == major && MinorVersion >= minor)))
				return true;
			else
				return false;
		}
	}

	struct SourceInfo {
		public string source_file;
		public byte[] guid, hash;
	}

	class DebugInfo {
		public int max_il_offset;
		public int[] il_offsets;
		public int[] line_numbers;
		public int[] column_numbers;
		public int[] end_line_numbers;
		public int[] end_column_numbers;
		public SourceInfo[] source_files;
	}

	struct FrameInfo {
		public long id;
		public long method;
		public int il_offset;
		public StackFrameFlags flags;
	}

	class TypeInfo {
		public string ns, name, full_name;
		public long assembly, module, base_type, element_type;
		public int token, rank, attributes;
		public bool is_byref, is_pointer, is_primitive, is_valuetype, is_enum;
		public bool is_gtd, is_generic_type;
		public long[] nested;
		public long gtd;
		public long[] type_args;
	}

	struct IfaceMapInfo {
		public long iface_id;
		public long[] iface_methods;
		public long[] target_methods;
	}

	class MethodInfo {
		public int attributes, iattributes, token;
		public bool is_gmd, is_generic_method;
		public long gmd;
		public long[] type_args;
	}

	class MethodBodyInfo {
		public byte[] il;
		public ExceptionClauseInfo[] clauses;
	}

	struct ExceptionClauseInfo {
		public ExceptionClauseFlags flags;
		public int try_offset;
		public int try_length;
		public int handler_offset;
		public int handler_length;
		public int filter_offset;
		public long catch_type_id;
	}

	[Flags]
	enum ExceptionClauseFlags {
		None = 0x0,
		Filter = 0x1,
		Finally = 0x2,
		Fault = 0x4,
	}

	struct ParamInfo {
		public int call_conv;
		public int param_count;
		public int generic_param_count;
		public long ret_type;
		public long[] param_types;
		public string[] param_names;
	}

	struct LocalsInfo {
		public long[] types;
		public string[] names;
		public int[] live_range_start;
		public int[] live_range_end;
	}

	struct PropInfo {
		public long id;
		public string name;
		public long get_method, set_method;
		public int attrs;
	}

	class CattrNamedArgInfo {
		public bool is_property;
		public long id;
		public ValueImpl value;
	}

	class CattrInfo {
		public long ctor_id;
		public ValueImpl[] ctor_args;
		public CattrNamedArgInfo[] named_args;
	}

	class ThreadInfo {
		public bool is_thread_pool;
	}

	struct ObjectRefInfo {
		public long type_id;
		public long domain_id;
	}

	enum ValueTypeId {
		VALUE_TYPE_ID_NULL = 0xf0,
		VALUE_TYPE_ID_TYPE = 0xf1,
		VALUE_TYPE_ID_PARENT_VTYPE = 0xf2
	}

	[Flags]
	enum InvokeFlags {
		NONE = 0,
		DISABLE_BREAKPOINTS = 1,
		SINGLE_THREADED = 2,
		OUT_THIS = 4,
		OUT_ARGS = 8,
		VIRTUAL = 16,
	}

	enum ElementType {
		End		 = 0x00,
		Void		= 0x01,
		Boolean	 = 0x02,
		Char		= 0x03,
		I1		  = 0x04,
		U1		  = 0x05,
		I2		  = 0x06,
		U2		  = 0x07,
		I4		  = 0x08,
		U4		  = 0x09,
		I8		  = 0x0a,
		U8		  = 0x0b,
		R4		  = 0x0c,
		R8		  = 0x0d,
		String	  = 0x0e,
		Ptr		 = 0x0f,
		ByRef	   = 0x10,
		ValueType   = 0x11,
		Class	   = 0x12,
		Var        = 0x13,
		Array	   = 0x14,
		GenericInst = 0x15,
		TypedByRef  = 0x16,
		I		   = 0x18,
		U		   = 0x19,
		FnPtr	   = 0x1b,
		Object	  = 0x1c,
		SzArray	 = 0x1d,
		MVar       = 0x1e,
		CModReqD	= 0x1f,
		CModOpt	 = 0x20,
		Internal	= 0x21,
		Modifier	= 0x40,
		Sentinel	= 0x41,
		Pinned	  = 0x45,

		Type		= 0x50,
		Boxed	   = 0x51,
		Enum		= 0x55
	}

	class ValueImpl {
		public ElementType Type; /* or one of the VALUE_TYPE_ID constants */
		public long Objid;
		public object Value;
		public long Klass; // For ElementType.ValueType
		public ValueImpl[] Fields; // for ElementType.ValueType
		public bool IsEnum; // For ElementType.ValueType
		public long Id; /* For VALUE_TYPE_ID_TYPE */
		public int Index; /* For VALUE_TYPE_PARENT_VTYPE */
	}

	class ModuleInfo {
		public string Name, ScopeName, FQName, Guid;
		public long Assembly;
	}		

	class FieldMirrorInfo {
		public string Name;
		public long Parent, TypeId;
		public int Attrs;
	}

	enum TokenType {
		STRING = 0,
		TYPE = 1,
		FIELD = 2,
		METHOD = 3,
		UNKNOWN = 4
	}

	[Flags]
	enum StackFrameFlags {
		NONE = 0,
		DEBUGGER_INVOKE = 1,
		NATIVE_TRANSITION = 2
	}

	class ResolvedToken {
		public TokenType Type;
		public string Str;
		public long Id;
	}

	class Modifier {
	}

	class CountModifier : Modifier {
		public int Count {
			get; set;
		}
	}

	class LocationModifier : Modifier {
		public long Method {
			get; set;
		}

		public long Location {
			get; set;
		}
	}

	class StepModifier : Modifier {
		public long Thread {
			get; set;
		}

		public int Depth {
			get; set;
		}

		public int Size {
			get; set;
		}

		public int Filter {
			get; set;
		}
	}

	class ThreadModifier : Modifier {
		public long Thread {
			get; set;
		}
	}

	class ExceptionModifier : Modifier {
		public long Type {
			get; set;
		}
		public bool Caught {
			get; set;
		}
		public bool Uncaught {
			get; set;
		}
		public bool Subclasses {
			get; set;
		}
	}

	class AssemblyModifier : Modifier {
		public long[] Assemblies {
			get; set;
		}
	}

	class SourceFileModifier : Modifier {
		public string[] SourceFiles {
			get; set;
		}
	}

	class TypeNameModifier : Modifier {
		public string[] TypeNames {
			get; set;
		}
	}

	class EventInfo {
		public EventType EventType {
			get; set;
		}

		public int ReqId {
			get; set;
		}

		public SuspendPolicy SuspendPolicy {
			get; set;
		}

		public long ThreadId {
			get; set;
		}

		public long Id {
			get; set;
		}

		public long Location {
			get; set;
		}

		public int Level {
			get; set;
		}

		public string Category {
			get; set;
		}

		public string Message {
			get; set;
		}

		public int ExitCode {
			get; set;
		}

		public EventInfo (EventType type, int req_id) {
			EventType = type;
			ReqId = req_id;
		}
	}

	public enum ErrorCode {
		NONE = 0,
		INVALID_OBJECT = 20,
		INVALID_FIELDID = 25,
		INVALID_FRAMEID = 30,
		NOT_IMPLEMENTED = 100,
		NOT_SUSPENDED = 101,
		INVALID_ARGUMENT = 102,
		ERR_UNLOADED = 103,
		ERR_NO_INVOCATION = 104,
		ABSENT_INFORMATION = 105,
		NO_SEQ_POINT_AT_IL_OFFSET = 106
	}

	public class ErrorHandlerEventArgs : EventArgs {

		public ErrorCode ErrorCode {
			get; set;
		}
	}

	/*
	 * Represents the connection to the debuggee
	 */
	public abstract class Connection
	{
		/*
		 * The protocol and the packet format is based on JDWP, the differences 
		 * are in the set of supported events, and the commands.
		 */
		internal const string HANDSHAKE_STRING = "DWP-Handshake";

		internal const int HEADER_LENGTH = 11;

		static readonly bool EnableConnectionLogging = !String.IsNullOrEmpty (Environment.GetEnvironmentVariable ("MONO_SDB_LOG"));
		static int ConnectionId;
		readonly StreamWriter LoggingStream;

		/*
		 * Th version of the wire-protocol implemented by the library. The library
		 * and the debuggee can communicate if they implement the same major version.
		 * If they implement a different minor version, they can communicate, but some
		 * features might not be available. This allows older clients to communicate
		 * with newer runtimes, and vice versa.
		 */
		internal const int MAJOR_VERSION = 2;
		internal const int MINOR_VERSION = 40;

		enum WPSuspendPolicy {
			NONE = 0,
			EVENT_THREAD = 1,
			ALL = 2
		}

		enum CommandSet {
			VM = 1,
			OBJECT_REF = 9,
			STRING_REF = 10,
			THREAD = 11,
			ARRAY_REF = 13,
			EVENT_REQUEST = 15,
			STACK_FRAME = 16,
			APPDOMAIN = 20,
			ASSEMBLY = 21,
			METHOD = 22,
			TYPE = 23,
			MODULE = 24,
			FIELD = 25,
			EVENT = 64
		}

		enum EventKind {
			VM_START = 0,
			VM_DEATH = 1,
			THREAD_START = 2,
			THREAD_DEATH = 3,
			APPDOMAIN_CREATE = 4, // Not in JDI
			APPDOMAIN_UNLOAD = 5, // Not in JDI
			METHOD_ENTRY = 6,
			METHOD_EXIT = 7,
			ASSEMBLY_LOAD = 8,
			ASSEMBLY_UNLOAD = 9,
			BREAKPOINT = 10,
			STEP = 11,
			TYPE_LOAD = 12,
			EXCEPTION = 13,
			KEEPALIVE = 14,
			USER_BREAK = 15,
			USER_LOG = 16
		}

		enum ModifierKind {
			COUNT = 1,
			THREAD_ONLY = 3,
			LOCATION_ONLY = 7,
			EXCEPTION_ONLY = 8,
			STEP = 10,
			ASSEMBLY_ONLY = 11,
			SOURCE_FILE_ONLY = 12,
			TYPE_NAME_ONLY = 13
		}

		enum CmdVM {
			VERSION = 1,
			ALL_THREADS = 2,
			SUSPEND = 3,
			RESUME = 4,
			EXIT = 5,
			DISPOSE = 6,
			INVOKE_METHOD = 7,
			SET_PROTOCOL_VERSION = 8,
			ABORT_INVOKE = 9,
			SET_KEEPALIVE = 10,
			GET_TYPES_FOR_SOURCE_FILE = 11,
			GET_TYPES = 12,
			INVOKE_METHODS = 13,
			START_BUFFERING = 14,
			STOP_BUFFERING = 15
		}

		enum CmdEvent {
			COMPOSITE = 100
		}

		enum CmdThread {
			GET_FRAME_INFO = 1,
			GET_NAME = 2,
			GET_STATE = 3,
			GET_INFO = 4,
			/* FIXME: Merge into GET_INFO when the major protocol version is increased */
			GET_ID = 5,
			/* Ditto */
			GET_TID = 6,
			SET_IP = 7
		}

		enum CmdEventRequest {
			SET = 1,
			CLEAR = 2,
			CLEAR_ALL_BREAKPOINTS = 3
		}

		enum CmdAppDomain {
			GET_ROOT_DOMAIN = 1,
			GET_FRIENDLY_NAME = 2,
			GET_ASSEMBLIES = 3,
			GET_ENTRY_ASSEMBLY = 4,
			CREATE_STRING = 5,
			GET_CORLIB = 6,
			CREATE_BOXED_VALUE = 7
		}

		enum CmdAssembly {
			GET_LOCATION = 1,
			GET_ENTRY_POINT = 2,
			GET_MANIFEST_MODULE = 3,
			GET_OBJECT = 4,
			GET_TYPE = 5,
			GET_NAME = 6
		}

		enum CmdModule {
			GET_INFO = 1,
		}

		enum CmdMethod {
			GET_NAME = 1,
			GET_DECLARING_TYPE = 2,
			GET_DEBUG_INFO = 3,
			GET_PARAM_INFO = 4,
			GET_LOCALS_INFO = 5,
			GET_INFO = 6,
			GET_BODY = 7,
			RESOLVE_TOKEN = 8,
			GET_CATTRS = 9,
			MAKE_GENERIC_METHOD = 10
		}

		enum CmdType {
			GET_INFO = 1,
			GET_METHODS = 2,
			GET_FIELDS = 3,
			GET_VALUES = 4,
			GET_OBJECT = 5,
			GET_SOURCE_FILES = 6,
			SET_VALUES = 7,
			IS_ASSIGNABLE_FROM = 8,
			GET_PROPERTIES = 9,
			GET_CATTRS = 10,
			GET_FIELD_CATTRS = 11,
			GET_PROPERTY_CATTRS = 12,
			/* FIXME: Merge into GET_SOURCE_FILES when the major protocol version is increased */
			GET_SOURCE_FILES_2 = 13,
			/* FIXME: Merge into GET_VALUES when the major protocol version is increased */
			GET_VALUES_2 = 14,
			CMD_TYPE_GET_METHODS_BY_NAME_FLAGS = 15,
			GET_INTERFACES = 16,
			GET_INTERFACE_MAP = 17,
			IS_INITIALIZED = 18,
			CREATE_INSTANCE = 19
		}

		enum CmdField {
			GET_INFO = 1
		}

		[Flags]
		enum BindingFlagsExtensions {
			BINDING_FLAGS_IGNORE_CASE = 0x70000000,
		}

		enum CmdStackFrame {
			GET_VALUES = 1,
			GET_THIS = 2,
			SET_VALUES = 3,
			GET_DOMAIN = 4,
		}

		enum CmdArrayRef {
			GET_LENGTH = 1,
			GET_VALUES = 2,
			SET_VALUES = 3
		}

		enum CmdStringRef {
			GET_VALUE = 1,
			GET_LENGTH = 2,
			GET_CHARS = 3
		}

		enum CmdObjectRef {
			GET_TYPE = 1,
			GET_VALUES = 2,
			IS_COLLECTED = 3,
			GET_ADDRESS = 4,
			GET_DOMAIN = 5,
			SET_VALUES = 6,
			GET_INFO = 7,
		}

		class Header {
			public int id;
			public int command_set;
			public int command;
			public int flags;
		}			

		internal static int GetPacketLength (byte[] header) {
			int offset = 0;
			return decode_int (header, ref offset);
		}

		internal static bool IsReplyPacket (byte[] packet) {
			int offset = 8;
			return decode_byte (packet, ref offset) == 0x80;
		}

		internal static int GetPacketId (byte[] packet) {
			int offset = 4;
			return decode_int (packet, ref offset);
		}

		static int decode_byte (byte[] packet, ref int offset) {
			return packet [offset++];
		}

		static int decode_short (byte[] packet, ref int offset) {
			int res = ((int)packet [offset] << 8) | (int)packet [offset + 1];
			offset += 2;
			return res;
		}

		static int decode_int (byte[] packet, ref int offset) {
			int res = ((int)packet [offset] << 24) | ((int)packet [offset + 1] << 16) | ((int)packet [offset + 2] << 8) | (int)packet [offset + 3];
			offset += 4;
			return res;
		}

		static long decode_id (byte[] packet, ref int offset) {
			return decode_int (packet, ref offset);
		}

		static long decode_long (byte[] packet, ref int offset) {
			uint high = (uint)decode_int (packet, ref offset);
			uint low = (uint)decode_int (packet, ref offset);

			return (long)(((ulong)high << 32) | (ulong)low);
		}

		internal static SuspendPolicy decode_suspend_policy (int suspend_policy) {
			switch ((WPSuspendPolicy)suspend_policy) {
			case WPSuspendPolicy.NONE:
				return SuspendPolicy.None;
			case WPSuspendPolicy.EVENT_THREAD:
				return SuspendPolicy.EventThread;
			case WPSuspendPolicy.ALL:
				return SuspendPolicy.All;
			default:
				throw new NotImplementedException ();
			}
		}

		static Header decode_command_header (byte[] packet) {
			int offset = 0;
			Header res = new Header ();

			decode_int (packet, ref offset);
			res.id = decode_int (packet, ref offset);
			res.flags = decode_byte (packet, ref offset);
			res.command_set = decode_byte (packet, ref offset);
			res.command = decode_byte (packet, ref offset);

			return res;
		}

		static void encode_byte (byte[] buf, int b, ref int offset) {
			buf [offset] = (byte)b;
			offset ++;
		}

		static void encode_int (byte[] buf, int i, ref int offset) {
			buf [offset] = (byte)((i >> 24) & 0xff);
			buf [offset + 1] = (byte)((i >> 16) & 0xff);
			buf [offset + 2] = (byte)((i >> 8) & 0xff);
			buf [offset + 3] = (byte)((i >> 0) & 0xff);
			offset += 4;
		}

		static void encode_id (byte[] buf, long id, ref int offset) {
			encode_int (buf, (int)id, ref offset);
		}

		static void encode_long (byte[] buf, long l, ref int offset) {
			encode_int (buf, (int)((l >> 32) & 0xffffffff), ref offset);
			encode_int (buf, (int)(l & 0xffffffff), ref offset);
		}

		internal static byte[] EncodePacket (int id, int commandSet, int command, byte[] data, int dataLen) {
			byte[] buf = new byte [dataLen + 11];
			int offset = 0;
			
			encode_int (buf, buf.Length, ref offset);
			encode_int (buf, id, ref offset);
			encode_byte (buf, 0, ref offset);
			encode_byte (buf, commandSet, ref offset);
			encode_byte (buf, command, ref offset);

			for (int i = 0; i < dataLen; ++i)
				buf [offset + i] = data [i];

			return buf;
		}

		class PacketReader {
			byte[] packet;
			int offset;

			public PacketReader (byte[] packet) {
				this.packet = packet;

				// For event packets
				Header header = decode_command_header (packet);
				CommandSet = (CommandSet)header.command_set;
				Command = header.command;

				// For reply packets
				offset = 0;
				ReadInt (); // length
				ReadInt (); // id
				ReadByte (); // flags
				ErrorCode = ReadShort ();
			}

			public CommandSet CommandSet {
				get; set;
			}

			public int Command {
				get; set;
			}

			public int ErrorCode {
				get; set;
			}

			public int Offset {
				get {
					return offset;
				}
			}

			public int ReadByte () {
				return decode_byte (packet, ref offset);
			}

			public int ReadShort () {
				return decode_short (packet, ref offset);
			}

			public int ReadInt () {
				return decode_int (packet, ref offset);
			}

			public long ReadId () {
				return decode_id (packet, ref offset);
			}

			public long ReadLong () {
				return decode_long (packet, ref offset);
			}

			public float ReadFloat () {
				float f = DataConverter.FloatFromBE (packet, offset);
				offset += 4;
				return f;
			}

			public double ReadDouble () {
				double d = DataConverter.DoubleFromBE (packet, offset);
				offset += 8;
				return d;
			}

			public string ReadString () {
				int len = decode_int (packet, ref offset);
				string res = new String (Encoding.UTF8.GetChars (packet, offset, len));
				offset += len;
				return res;
			}

			public ValueImpl ReadValue () {
				ElementType etype = (ElementType)ReadByte ();

				switch (etype) {
				case ElementType.Void:
					return new ValueImpl { Type = etype };
				case ElementType.I1:
					return new ValueImpl { Type = etype, Value = (sbyte)ReadInt () };
				case ElementType.U1:
					return new ValueImpl { Type = etype, Value = (byte)ReadInt () };
				case ElementType.Boolean:
					return new ValueImpl { Type = etype, Value = ReadInt () != 0 };
				case ElementType.I2:
					return new ValueImpl { Type = etype, Value = (short)ReadInt () };
				case ElementType.U2:
					return new ValueImpl { Type = etype, Value = (ushort)ReadInt () };
				case ElementType.Char:
					return new ValueImpl { Type = etype, Value = (char)ReadInt () };
				case ElementType.I4:
					return new ValueImpl { Type = etype, Value = ReadInt () };
				case ElementType.U4:
					return new ValueImpl { Type = etype, Value = (uint)ReadInt () };
				case ElementType.I8:
					return new ValueImpl { Type = etype, Value = ReadLong () };
				case ElementType.U8:
					return new ValueImpl { Type = etype, Value = (ulong)ReadLong () };
				case ElementType.R4:
					return new ValueImpl { Type = etype, Value = ReadFloat () };
				case ElementType.R8:
					return new ValueImpl { Type = etype, Value = ReadDouble () };
				case ElementType.I:
				case ElementType.U:
				case ElementType.Ptr:
					// FIXME: The client and the debuggee might have different word sizes
					return new ValueImpl { Type = etype, Value = ReadLong () };
				case ElementType.String:
				case ElementType.SzArray:
				case ElementType.Class:
				case ElementType.Array:
				case ElementType.Object:
					long objid = ReadId ();
					return new ValueImpl () { Type = etype, Objid = objid };
				case ElementType.ValueType:
					bool is_enum = ReadByte () == 1;
					long klass = ReadId ();
					long nfields = ReadInt ();
					ValueImpl[] fields = new ValueImpl [nfields];
					for (int i = 0; i < nfields; ++i)
						fields [i] = ReadValue ();
					return new ValueImpl () { Type = etype, Klass = klass, Fields = fields, IsEnum = is_enum };
				case (ElementType)ValueTypeId.VALUE_TYPE_ID_NULL:
					return new ValueImpl { Type = etype };
				case (ElementType)ValueTypeId.VALUE_TYPE_ID_TYPE:
					return new ValueImpl () { Type = etype, Id = ReadId () };
				case (ElementType)ValueTypeId.VALUE_TYPE_ID_PARENT_VTYPE:
					return new ValueImpl () { Type = etype, Index = ReadInt () };
				default:
					throw new NotImplementedException ("Unable to handle type " + etype);
				}
			}

			public long[] ReadIds (int n) {
				long[] res = new long [n];
				for (int i = 0; i < n; ++i)
					res [i] = ReadId ();
				return res;
			}
		}

		class PacketWriter {

			byte[] data;
			int offset;

			public PacketWriter () {
				data = new byte [1024];
				offset = 0;
			}

			void MakeRoom (int size) {
				if (offset + size >= data.Length) {
					int new_len = data.Length * 2;
					while (new_len < offset + size) {
						new_len *= 2;
					}
					byte[] new_data = new byte [new_len];
					Array.Copy (data, new_data, data.Length);
					data = new_data;
				}
			}

			public PacketWriter WriteByte (byte val) {
				MakeRoom (1);
				encode_byte (data, val, ref offset);
				return this;
			}

			public PacketWriter WriteInt (int val) {
				MakeRoom (4);
				encode_int (data, val, ref offset);
				return this;
			}

			public PacketWriter WriteId (long id) {
				MakeRoom (8);
				encode_id (data, id, ref offset);
				return this;
			}

			public PacketWriter WriteLong (long val) {
				MakeRoom (8);
				encode_long (data, val, ref offset);
				return this;
			}

			public PacketWriter WriteFloat (float f) {
				MakeRoom (8);
				byte[] b = DataConverter.GetBytesBE (f);
				for (int i = 0; i < 4; ++i)
					data [offset + i] = b [i];
				offset += 4;
				return this;
			}

			public PacketWriter WriteDouble (double d) {
				MakeRoom (8);
				byte[] b = DataConverter.GetBytesBE (d);
				for (int i = 0; i < 8; ++i)
					data [offset + i] = b [i];
				offset += 8;
				return this;
			}

			public PacketWriter WriteInts (int[] ids) {
				for (int i = 0; i < ids.Length; ++i)
					WriteInt (ids [i]);
				return this;
			}

			public PacketWriter WriteIds (long[] ids) {
				for (int i = 0; i < ids.Length; ++i)
					WriteId (ids [i]);
				return this;
			}

			public PacketWriter WriteString (string s) {
				if (s == null)
					return WriteInt (-1);

				byte[] b = Encoding.UTF8.GetBytes (s);
				MakeRoom (4);
				encode_int (data, b.Length, ref offset);
				MakeRoom (b.Length);
				Buffer.BlockCopy (b, 0, data, offset, b.Length);
				offset += b.Length;
				return this;
			}

			public PacketWriter WriteBool (bool val) {
				WriteByte (val ? (byte)1 : (byte)0);
				return this;
			}

			public PacketWriter WriteValue (ValueImpl v) {
				ElementType t;

				if (v.Value != null)
					t = TypeCodeToElementType (Type.GetTypeCode (v.Value.GetType ()));
				else
					t = v.Type;
				WriteByte ((byte)t);
				switch (t) {
				case ElementType.Boolean:
					WriteInt ((bool)v.Value ? 1 : 0);
					break;
				case ElementType.Char:
					WriteInt ((int)(char)v.Value);
					break;
				case ElementType.I1:
					WriteInt ((int)(sbyte)v.Value);
					break;
				case ElementType.U1:
					WriteInt ((int)(byte)v.Value);
					break;
				case ElementType.I2:
					WriteInt ((int)(short)v.Value);
					break;
				case ElementType.U2:
					WriteInt ((int)(ushort)v.Value);
					break;
				case ElementType.I4:
					WriteInt ((int)(int)v.Value);
					break;
				case ElementType.U4:
					WriteInt ((int)(uint)v.Value);
					break;
				case ElementType.I8:
					WriteLong ((long)(long)v.Value);
					break;
				case ElementType.U8:
					WriteLong ((long)(ulong)v.Value);
					break;
				case ElementType.R4:
					WriteFloat ((float)v.Value);
					break;
				case ElementType.R8:
					WriteDouble ((double)v.Value);
					break;
				case ElementType.String:
				case ElementType.SzArray:
				case ElementType.Class:
				case ElementType.Array:
				case ElementType.Object:
					WriteId (v.Objid);
					break;
				case ElementType.ValueType:
					// FIXME: 
					if (v.IsEnum)
						throw new NotImplementedException ();
					WriteByte (0);
					WriteId (v.Klass);
					WriteInt (v.Fields.Length);
					for (int i = 0; i < v.Fields.Length; ++i)
						WriteValue (v.Fields [i]);
					break;
				case (ElementType)ValueTypeId.VALUE_TYPE_ID_NULL:
					break;
				default:
					throw new NotImplementedException ();
				}

				return this;
			}

			public PacketWriter WriteValues (ValueImpl[] values) {
				for (int i = 0; i < values.Length; ++i)
					WriteValue (values [i]);
				return this;
			}

			public byte[] Data {
				get {
					return data;
				}
			}

			public int Offset {
				get {
					return offset;
				}
			}
		}

		delegate void ReplyCallback (int packet_id, byte[] packet);

		bool closed;
		Thread receiver_thread;
		Dictionary<int, byte[]> reply_packets;
		Dictionary<int, ReplyCallback> reply_cbs;
		Dictionary<int, int> reply_cb_counts;
		object reply_packets_monitor;

		internal event EventHandler<ErrorHandlerEventArgs> ErrorHandler;

		protected Connection () {
			closed = false;
			reply_packets = new Dictionary<int, byte[]> ();
			reply_cbs = new Dictionary<int, ReplyCallback> ();
			reply_cb_counts = new Dictionary<int, int> ();
			reply_packets_monitor = new Object ();
			if (EnableConnectionLogging) {
				var path = Environment.GetEnvironmentVariable ("MONO_SDB_LOG");
				if (path.Contains ("{0}")) {
					//C:\SomeDir\sdbLog{0}.txt -> C:\SomeDir\sdbLog1.txt
					LoggingStream = new StreamWriter (string.Format (path, ConnectionId++), false);
				} else if (Path.HasExtension (path)) {
					//C:\SomeDir\sdbLog.txt -> C:\SomeDir\sdbLog1.txt
					LoggingStream = new StreamWriter (Path.GetDirectoryName (path) + Path.DirectorySeparatorChar + Path.GetFileNameWithoutExtension (path) + ConnectionId++ + "." + Path.GetExtension (path), false);
				} else {
					//C:\SomeDir\sdbLog -> C:\SomeDir\sdbLog1
					LoggingStream = new StreamWriter (path + ConnectionId++, false);
				}
			}
		}
		
		protected abstract int TransportReceive (byte[] buf, int buf_offset, int len);
		protected abstract int TransportSend (byte[] buf, int buf_offset, int len);
		protected abstract void TransportSetTimeouts (int send_timeout, int receive_timeout);
		protected abstract void TransportClose ();

		internal VersionInfo Version;
		
		int Receive (byte[] buf, int buf_offset, int len) {
			int offset = 0;

			while (offset < len) {
				int n = TransportReceive (buf, buf_offset + offset, len - offset);

				if (n == 0)
					return offset;
				offset += n;
			}

			return offset;
		}
		
		// Do the wire protocol handshake
		internal void Connect () {
			byte[] buf = new byte [HANDSHAKE_STRING.Length];
			char[] cbuf = new char [buf.Length];

			// FIXME: Add a timeout
			int n = Receive (buf, 0, buf.Length);
			if (n == 0)
				throw new IOException ("DWP Handshake failed.");
			for (int i = 0; i < buf.Length; ++i)
				cbuf [i] = (char)buf [i];

			if (new String (cbuf) != HANDSHAKE_STRING)
				throw new IOException ("DWP Handshake failed.");
			
			TransportSend (buf, 0, buf.Length);

			receiver_thread = new Thread (new ThreadStart (receiver_thread_main));
			receiver_thread.Name = "SDB Receiver";
			receiver_thread.IsBackground = true;
			receiver_thread.Start ();

			Version = VM_GetVersion ();

			//
			// Tell the debuggee our protocol version, so newer debuggees can work
			// with older clients
			//

			//
			// Older debuggees might not support this request
			EventHandler<ErrorHandlerEventArgs> OrigErrorHandler = ErrorHandler;
			ErrorHandler = null;
			ErrorHandler += delegate (object sender, ErrorHandlerEventArgs args) {
				throw new NotSupportedException ();
			};
			try {
				VM_SetProtocolVersion (MAJOR_VERSION, MINOR_VERSION);
			} catch (NotSupportedException) {
			}
			ErrorHandler = OrigErrorHandler;
		}

		internal byte[] ReadPacket () {
			// FIXME: Throw ClosedConnectionException () if the connection is closed
			// FIXME: Throw ClosedConnectionException () if another thread closes the connection
			// FIXME: Locking
			byte[] header = new byte [HEADER_LENGTH];

			int len = Receive (header, 0, header.Length);
			if (len == 0)
				return new byte [0];
			if (len != HEADER_LENGTH) {
				// FIXME:
				throw new IOException ("Packet of length " + len + " is read.");
			}

			int packetLength = GetPacketLength (header);
			if (packetLength < 11)
				throw new IOException ("Invalid packet length.");

			if (packetLength == 11) {
				return header;
			} else {
				byte[] buf = new byte [packetLength];
				for (int i = 0; i < header.Length; ++i)
					buf [i] = header [i];
				len = Receive (buf, header.Length, packetLength - header.Length);
				if (len != packetLength - header.Length)
					throw new IOException ();
				return buf;
			}
		}

		internal void WritePacket (byte[] packet) {
			// FIXME: Throw ClosedConnectionException () if the connection is closed
			// FIXME: Throw ClosedConnectionException () if another thread closes the connection
			// FIXME: Locking
			TransportSend (packet, 0, packet.Length);
		}

		internal void WritePackets (List<byte[]> packets) {
			// FIXME: Throw ClosedConnectionException () if the connection is closed
			// FIXME: Throw ClosedConnectionException () if another thread closes the connection
			// FIXME: Locking
			int len = 0;
			for (int i = 0; i < packets.Count; ++i)
				len += packets [i].Length;
			byte[] data = new byte [len];
			int pos = 0;
			for (int i = 0; i < packets.Count; ++i) {
				Buffer.BlockCopy (packets [i], 0, data, pos, packets [i].Length);
				pos += packets [i].Length;
			}
			TransportSend (data, 0, data.Length);
		}

		internal void Close () {
			closed = true;
		}

		internal bool IsClosed {
			get {
				return closed;
			}
		}

		bool disconnected;

		internal ManualResetEvent DisconnectedEvent = new ManualResetEvent (false);

		void receiver_thread_main () {
			while (!closed) {
				try {
					bool res = ReceivePacket ();
					if (!res)
						break;
				} catch (Exception ex) {
					if (!closed) {
						Console.WriteLine (ex);
					}
					break;
				}
			}

			lock (reply_packets_monitor) {
				disconnected = true;
				DisconnectedEvent.Set ();
				Monitor.PulseAll (reply_packets_monitor);
				TransportClose ();
			}
			EventHandler.VMDisconnect (0, 0, null);
		}

		bool ReceivePacket () {
				byte[] packet = ReadPacket ();

				if (packet.Length == 0) {
					return false;
				}

				if (IsReplyPacket (packet)) {
					int id = GetPacketId (packet);
					ReplyCallback cb = null;
					lock (reply_packets_monitor) {
						reply_cbs.TryGetValue (id, out cb);
						if (cb == null) {
							reply_packets [id] = packet;
							Monitor.PulseAll (reply_packets_monitor);
						} else {
							int c = reply_cb_counts [id];
							c --;
							if (c == 0) {
								reply_cbs.Remove (id);
								reply_cb_counts.Remove (id);
							}
						}
					}

					if (cb != null)
						cb.Invoke (id, packet);
				} else {
					PacketReader r = new PacketReader (packet);

					if (r.CommandSet == CommandSet.EVENT && r.Command == (int)CmdEvent.COMPOSITE) {
						int spolicy = r.ReadByte ();
						int nevents = r.ReadInt ();

						SuspendPolicy suspend_policy = decode_suspend_policy (spolicy);

						EventInfo[] events = new EventInfo [nevents];

						for (int i = 0; i < nevents; ++i) {
							EventKind kind = (EventKind)r.ReadByte ();
							int req_id = r.ReadInt ();

							EventType etype = (EventType)kind;

							long thread_id = r.ReadId ();
							if (kind == EventKind.VM_START) {
								events [i] = new EventInfo (etype, req_id) { ThreadId = thread_id };
								//EventHandler.VMStart (req_id, thread_id, null);
							} else if (kind == EventKind.VM_DEATH) {
								int exit_code = 0;
								if (Version.AtLeast (2, 27))
									exit_code = r.ReadInt ();
								//EventHandler.VMDeath (req_id, 0, null);
								events [i] = new EventInfo (etype, req_id) { ExitCode = exit_code };
							} else if (kind == EventKind.THREAD_START) {
								events [i] = new EventInfo (etype, req_id) { ThreadId = thread_id, Id = thread_id };
								//EventHandler.ThreadStart (req_id, thread_id, thread_id);
							} else if (kind == EventKind.THREAD_DEATH) {
								events [i] = new EventInfo (etype, req_id) { ThreadId = thread_id, Id = thread_id };
								//EventHandler.ThreadDeath (req_id, thread_id, thread_id);
							} else if (kind == EventKind.ASSEMBLY_LOAD) {
								long id = r.ReadId ();
								events [i] = new EventInfo (etype, req_id) { ThreadId = thread_id, Id = id };
								//EventHandler.AssemblyLoad (req_id, thread_id, id);
							} else if (kind == EventKind.ASSEMBLY_UNLOAD) {
								long id = r.ReadId ();
								events [i] = new EventInfo (etype, req_id) { ThreadId = thread_id, Id = id };
								//EventHandler.AssemblyUnload (req_id, thread_id, id);
							} else if (kind == EventKind.TYPE_LOAD) {
								long id = r.ReadId ();
								events [i] = new EventInfo (etype, req_id) { ThreadId = thread_id, Id = id };
								//EventHandler.TypeLoad (req_id, thread_id, id);
							} else if (kind == EventKind.METHOD_ENTRY) {
								long id = r.ReadId ();
								events [i] = new EventInfo (etype, req_id) { ThreadId = thread_id, Id = id };
								//EventHandler.MethodEntry (req_id, thread_id, id);
							} else if (kind == EventKind.METHOD_EXIT) {
								long id = r.ReadId ();
								events [i] = new EventInfo (etype, req_id) { ThreadId = thread_id, Id = id };
								//EventHandler.MethodExit (req_id, thread_id, id);
							} else if (kind == EventKind.BREAKPOINT) {
								long id = r.ReadId ();
								long loc = r.ReadLong ();
								events [i] = new EventInfo (etype, req_id) { ThreadId = thread_id, Id = id, Location = loc };
								//EventHandler.Breakpoint (req_id, thread_id, id, loc);
							} else if (kind == EventKind.STEP) {
								long id = r.ReadId ();
								long loc = r.ReadLong ();
								events [i] = new EventInfo (etype, req_id) { ThreadId = thread_id, Id = id, Location = loc };
								//EventHandler.Step (req_id, thread_id, id, loc);
							} else if (kind == EventKind.EXCEPTION) {
								long id = r.ReadId ();
								long loc = 0; // FIXME
								events [i] = new EventInfo (etype, req_id) { ThreadId = thread_id, Id = id, Location = loc };
								//EventHandler.Exception (req_id, thread_id, id, loc);
							} else if (kind == EventKind.APPDOMAIN_CREATE) {
								long id = r.ReadId ();
								events [i] = new EventInfo (etype, req_id) { ThreadId = thread_id, Id = id };
								//EventHandler.AppDomainCreate (req_id, thread_id, id);
							} else if (kind == EventKind.APPDOMAIN_UNLOAD) {
								long id = r.ReadId ();
								events [i] = new EventInfo (etype, req_id) { ThreadId = thread_id, Id = id };
								//EventHandler.AppDomainUnload (req_id, thread_id, id);
							} else if (kind == EventKind.USER_BREAK) {
								long id = 0;
								long loc = 0;
								events [i] = new EventInfo (etype, req_id) { ThreadId = thread_id, Id = id, Location = loc };
								//EventHandler.Exception (req_id, thread_id, id, loc);
							} else if (kind == EventKind.USER_LOG) {
								int level = r.ReadInt ();
								string category = r.ReadString ();
								string message = r.ReadString ();
								events [i] = new EventInfo (etype, req_id) { ThreadId = thread_id, Level = level, Category = category, Message = message };
								//EventHandler.Exception (req_id, thread_id, id, loc);
							} else if (kind == EventKind.KEEPALIVE) {
								events [i] = new EventInfo (etype, req_id) { };
							} else {
								throw new NotImplementedException ("Unknown event kind: " + kind);
							}
						}

						EventHandler.Events (suspend_policy, events);
					}
				}

				return true;
		}

		internal IEventHandler EventHandler {
			get; set;
		}

		static String CommandString (CommandSet command_set, int command)
		{
			string cmd;
			switch (command_set) {
			case CommandSet.VM:
				cmd = ((CmdVM)command).ToString ();
				break;
			case CommandSet.OBJECT_REF:
				cmd = ((CmdObjectRef)command).ToString ();
				break;
			case CommandSet.STRING_REF:
				cmd = ((CmdStringRef)command).ToString ();
				break;
			case CommandSet.THREAD:
				cmd = ((CmdThread)command).ToString ();
				break;
			case CommandSet.ARRAY_REF:
				cmd = ((CmdArrayRef)command).ToString ();
				break;
			case CommandSet.EVENT_REQUEST:
				cmd = ((CmdEventRequest)command).ToString ();
				break;
			case CommandSet.STACK_FRAME:
				cmd = ((CmdStackFrame)command).ToString ();
				break;
			case CommandSet.APPDOMAIN:
				cmd = ((CmdAppDomain)command).ToString ();
				break;
			case CommandSet.ASSEMBLY:
				cmd = ((CmdAssembly)command).ToString ();
				break;
			case CommandSet.METHOD:
				cmd = ((CmdMethod)command).ToString ();
				break;
			case CommandSet.TYPE:
				cmd = ((CmdType)command).ToString ();
				break;
			case CommandSet.MODULE:
				cmd = ((CmdModule)command).ToString ();
				break;
			case CommandSet.FIELD:
				cmd = ((CmdField)command).ToString ();
				break;
			case CommandSet.EVENT:
				cmd = ((CmdEvent)command).ToString ();
				break;
			default:
				cmd = command.ToString ();
				break;
			}
			return string.Format ("[{0} {1}]", command_set, cmd);
		}

		long total_protocol_ticks;

		void LogPacket (int packet_id, byte[] encoded_packet, byte[] reply_packet, CommandSet command_set, int command, Stopwatch watch) {
			watch.Stop ();
			total_protocol_ticks += watch.ElapsedTicks;
			var ts = TimeSpan.FromTicks (total_protocol_ticks);
			string msg = string.Format ("Packet: {0} sent: {1} received: {2} ms: {3} total ms: {4} {5}",
			   packet_id, encoded_packet.Length, reply_packet.Length, watch.ElapsedMilliseconds,
			   (ts.Seconds * 1000) + ts.Milliseconds,
			   CommandString (command_set, command));

			LoggingStream.WriteLine (msg);
			LoggingStream.Flush ();
		}

		bool buffer_packets;
		List<byte[]> buffered_packets = new List<byte[]> ();

		//
		// Start buffering request/response packets on both the client and the debuggee side.
		// Packets sent between StartBuffering ()/StopBuffering () must be async, i.e. sent
		// using Send () and not SendReceive ().
		//
		public void StartBuffering () {
			buffer_packets = true;
			if (Version.AtLeast (2, 34))
				VM_StartBuffering ();
		}

		public void StopBuffering () {
			if (Version.AtLeast (2, 34))
				VM_StopBuffering ();
			buffer_packets = false;

			WritePackets (buffered_packets);
			if (EnableConnectionLogging) {
				LoggingStream.WriteLine (String.Format ("Sent {0} packets.", buffered_packets.Count));
				LoggingStream.Flush ();
			}
			buffered_packets.Clear ();
		}

		/* Send a request and call cb when a result is received */
		int Send (CommandSet command_set, int command, PacketWriter packet, Action<PacketReader> cb, int count) {
			int id = IdGenerator;

			Stopwatch watch = null;
			if (EnableConnectionLogging)
				watch = Stopwatch.StartNew ();

			byte[] encoded_packet;
			if (packet == null)
				encoded_packet = EncodePacket (id, (int)command_set, command, null, 0);
			else
				encoded_packet = EncodePacket (id, (int)command_set, command, packet.Data, packet.Offset);

			if (cb != null) {
				lock (reply_packets_monitor) {
					reply_cbs [id] = delegate (int packet_id, byte[] p) {
						if (EnableConnectionLogging)
							LogPacket (packet_id, encoded_packet, p, command_set, command, watch);
						/* Run the callback on a tp thread to avoid blocking the receive thread */
						PacketReader r = new PacketReader (p);
						cb.BeginInvoke (r, null, null);
					};
					reply_cb_counts [id] = count;
				}
			}

			if (buffer_packets)
				buffered_packets.Add (encoded_packet);
			else
				WritePacket (encoded_packet);

			return id;
		}

		// Send a request without waiting for an answer
		void Send (CommandSet command_set, int command) {
			Send (command_set, command, null, null, 0);
		}

		PacketReader SendReceive (CommandSet command_set, int command, PacketWriter packet) {
			int id = IdGenerator;
			Stopwatch watch = null;

			if (disconnected)
				throw new VMDisconnectedException ();

			if (EnableConnectionLogging)
				watch = Stopwatch.StartNew ();

			byte[] encoded_packet;

			if (packet == null)
				encoded_packet = EncodePacket (id, (int)command_set, command, null, 0);
			else
				encoded_packet = EncodePacket (id, (int)command_set, command, packet.Data, packet.Offset);

			WritePacket (encoded_packet);

			int packetId = id;

			/* Wait for the reply packet */
			while (true) {
				lock (reply_packets_monitor) {
					if (reply_packets.ContainsKey (packetId)) {
						byte[] reply = reply_packets [packetId];
						reply_packets.Remove (packetId);
						PacketReader r = new PacketReader (reply);

						if (EnableConnectionLogging)
							LogPacket (packetId, encoded_packet, reply, command_set, command, watch);
						if (r.ErrorCode != 0) {
							if (ErrorHandler != null)
								ErrorHandler (this, new ErrorHandlerEventArgs () { ErrorCode = (ErrorCode)r.ErrorCode });
							throw new NotImplementedException ("No error handler set.");
						} else {
							return r;
						}
					} else {
						if (disconnected)
							throw new VMDisconnectedException ();
						Monitor.Wait (reply_packets_monitor);
					}
				}
			}
		}

		PacketReader SendReceive (CommandSet command_set, int command) {
			return SendReceive (command_set, command, null);
		}

		int packet_id_generator;

		int IdGenerator {
			get {
				return Interlocked.Increment (ref packet_id_generator);
			}
		}

		CattrInfo[] ReadCattrs (PacketReader r) {
			CattrInfo[] res = new CattrInfo [r.ReadInt ()];
			for (int i = 0; i < res.Length; ++i) {
				CattrInfo info = new CattrInfo ();
				info.ctor_id = r.ReadId ();
				info.ctor_args = new ValueImpl [r.ReadInt ()];
				for (int j = 0; j < info.ctor_args.Length; ++j) {
					info.ctor_args [j] = r.ReadValue ();
				}
				info.named_args = new CattrNamedArgInfo [r.ReadInt ()];
				for (int j = 0; j < info.named_args.Length; ++j) {
					CattrNamedArgInfo arg = new CattrNamedArgInfo ();
					int arg_type = r.ReadByte ();
					arg.is_property = arg_type == 0x54;
					arg.id = r.ReadId ();
					arg.value = r.ReadValue ();
					info.named_args [j] = arg;
				}
				res [i] = info;
			}
			return res;
		}

		static ElementType TypeCodeToElementType (TypeCode c) {
			switch (c) {
			case TypeCode.Boolean:
				return ElementType.Boolean;
			case TypeCode.Char:
				return ElementType.Char;
			case TypeCode.SByte:
				return ElementType.I1;
			case TypeCode.Byte:
				return ElementType.U1;
			case TypeCode.Int16:
				return ElementType.I2;
			case TypeCode.UInt16:
				return ElementType.U2;
			case TypeCode.Int32:
				return ElementType.I4;
			case TypeCode.UInt32:
				return ElementType.U4;
			case TypeCode.Int64:
				return ElementType.I8;
			case TypeCode.UInt64:
				return ElementType.U8;
			case TypeCode.Single:
				return ElementType.R4;
			case TypeCode.Double:
				return ElementType.R8;
			default:
				throw new NotImplementedException ();
			}
		}

		/*
		 * Implementation of debugger commands
		 */

		internal VersionInfo VM_GetVersion () {
			var res = SendReceive (CommandSet.VM, (int)CmdVM.VERSION, null);
			VersionInfo info = new VersionInfo ();
			info.VMVersion = res.ReadString ();
			info.MajorVersion = res.ReadInt ();
			info.MinorVersion = res.ReadInt ();
			return info;
		}

		internal void VM_SetProtocolVersion (int major, int minor) {
			SendReceive (CommandSet.VM, (int)CmdVM.SET_PROTOCOL_VERSION, new PacketWriter ().WriteInt (major).WriteInt (minor));
		}

		internal void VM_GetThreads (Action<long[]> resultCallaback) {
			Send (CommandSet.VM, (int)CmdVM.ALL_THREADS, null, (res) => {
				int len = res.ReadInt ();
				long[] arr = new long [len];
				for (int i = 0; i < len; ++i)
					arr [i] = res.ReadId ();
				resultCallaback(arr);
			}, 1);
		}

		internal void VM_Suspend () {
			SendReceive (CommandSet.VM, (int)CmdVM.SUSPEND);
		}

		internal void VM_Resume () {
			SendReceive (CommandSet.VM, (int)CmdVM.RESUME);
		}

		internal void VM_Exit (int exitCode) {
			SendReceive (CommandSet.VM, (int)CmdVM.EXIT, new PacketWriter ().WriteInt (exitCode));
		}

		internal void VM_Dispose () {
			SendReceive (CommandSet.VM, (int)CmdVM.DISPOSE);
		}

		internal ValueImpl VM_InvokeMethod (long thread, long method, ValueImpl this_arg, ValueImpl[] arguments, InvokeFlags flags, out ValueImpl exc) {
			exc = null;
			PacketReader r = SendReceive (CommandSet.VM, (int)CmdVM.INVOKE_METHOD, new PacketWriter ().WriteId (thread).WriteInt ((int)flags).WriteId (method).WriteValue (this_arg).WriteInt (arguments.Length).WriteValues (arguments));
			if (r.ReadByte () == 0) {
				exc = r.ReadValue ();
				return null;
			} else {
				return r.ReadValue ();
			}
		}

		internal delegate void InvokeMethodCallback (ValueImpl v, ValueImpl exc, ValueImpl out_this, ValueImpl[] out_args, ErrorCode error, object state);

		void read_invoke_res (PacketReader r, out ValueImpl v, out ValueImpl exc, out ValueImpl out_this, out ValueImpl[] out_args) {
			int resflags = r.ReadByte ();
			v = null;
			exc = null;
			out_this = null;
			out_args = null;
			if (resflags == 0) {
				exc = r.ReadValue ();
			} else {
				v = r.ReadValue ();
				if ((resflags & 2) != 0)
					out_this = r.ReadValue ();
				if ((resflags & 4) != 0) {
					int nargs = r.ReadInt ();
					out_args = new ValueImpl [nargs];
					for (int i = 0; i < nargs; ++i)
						out_args [i] = r.ReadValue ();
				}
			}
		}

		internal int VM_BeginInvokeMethod (long thread, long method, ValueImpl this_arg, ValueImpl[] arguments, InvokeFlags flags, InvokeMethodCallback callback, object state) {
			return Send (CommandSet.VM, (int)CmdVM.INVOKE_METHOD, new PacketWriter ().WriteId (thread).WriteInt ((int)flags).WriteId (method).WriteValue (this_arg).WriteInt (arguments.Length).WriteValues (arguments), delegate (PacketReader r) {
					ValueImpl v, exc, out_this = null;
					ValueImpl[] out_args = null;

					if (r.ErrorCode != 0) {
						callback (null, null, null, null, (ErrorCode)r.ErrorCode, state);
					} else {
						read_invoke_res (r, out v, out exc, out out_this, out out_args);
						callback (v, exc, out_this, out_args, 0, state);
					}
				}, 1);
		}

		internal int VM_BeginInvokeMethods (long thread, long[] methods, ValueImpl this_arg, List<ValueImpl[]> arguments, InvokeFlags flags, InvokeMethodCallback callback, object state) {
			// FIXME: Merge this with INVOKE_METHOD
			var w = new PacketWriter ();
			w.WriteId (thread);
			w.WriteInt ((int)flags);
			w.WriteInt (methods.Length);
			for (int i = 0; i < methods.Length; ++i) {
				w.WriteId (methods [i]);
				w.WriteValue (this_arg);
				w.WriteInt (arguments [i].Length);
				w.WriteValues (arguments [i]);
			}
			return Send (CommandSet.VM, (int)CmdVM.INVOKE_METHODS, w, delegate (PacketReader r) {
					ValueImpl v, exc, out_this = null;
					ValueImpl[] out_args = null;

					if (r.ErrorCode != 0) {
						callback (null, null, null, null, (ErrorCode)r.ErrorCode, state);
					} else {
						read_invoke_res (r, out v, out exc, out out_this, out out_args);
						callback (v, exc, out_this, out_args, 0, state);
					}
				}, methods.Length);
		}

		internal void VM_AbortInvoke (long thread, int id)
		{
			SendReceive (CommandSet.VM, (int)CmdVM.ABORT_INVOKE, new PacketWriter ().WriteId (thread).WriteInt (id));
		}

		internal void SetSocketTimeouts (int send_timeout, int receive_timeout, int keepalive_interval)
		{
			TransportSetTimeouts (send_timeout, receive_timeout);
			SendReceive (CommandSet.VM, (int)CmdVM.SET_KEEPALIVE, new PacketWriter ().WriteId (keepalive_interval));
		}

		internal long[] VM_GetTypesForSourceFile (string fname, bool ignoreCase) {
			var res = SendReceive (CommandSet.VM, (int)CmdVM.GET_TYPES_FOR_SOURCE_FILE, new PacketWriter ().WriteString (fname).WriteBool (ignoreCase));
			int count = res.ReadInt ();
			long[] types = new long [count];
			for (int i = 0; i < count; ++i)
				types [i] = res.ReadId ();
			return types;
		}

		internal long[] VM_GetTypes (string name, bool ignoreCase) {
			var res = SendReceive (CommandSet.VM, (int)CmdVM.GET_TYPES, new PacketWriter ().WriteString (name).WriteBool (ignoreCase));
			int count = res.ReadInt ();
			long[] types = new long [count];
			for (int i = 0; i < count; ++i)
				types [i] = res.ReadId ();
			return types;
		}

		internal void VM_StartBuffering () {
			Send (CommandSet.VM, (int)CmdVM.START_BUFFERING);
		}

		internal void VM_StopBuffering () {
			Send (CommandSet.VM, (int)CmdVM.STOP_BUFFERING);
		}

		/*
		 * DOMAIN
		 */

		internal long RootDomain {
			get {
				return SendReceive (CommandSet.APPDOMAIN, (int)CmdAppDomain.GET_ROOT_DOMAIN, null).ReadId ();
			}
		}

		internal string Domain_GetName (long id) {
			return SendReceive (CommandSet.APPDOMAIN, (int)CmdAppDomain.GET_FRIENDLY_NAME, new PacketWriter ().WriteId (id)).ReadString ();
		}

		internal long[] Domain_GetAssemblies (long id) {
			var res = SendReceive (CommandSet.APPDOMAIN, (int)CmdAppDomain.GET_ASSEMBLIES, new PacketWriter ().WriteId (id));
			int count = res.ReadInt ();
			long[] assemblies = new long [count];
			for (int i = 0; i < count; ++i)
				assemblies [i] = res.ReadId ();
			return assemblies;
		}

		internal long Domain_GetEntryAssembly (long id) {
			return SendReceive (CommandSet.APPDOMAIN, (int)CmdAppDomain.GET_ENTRY_ASSEMBLY, new PacketWriter ().WriteId (id)).ReadId ();
		}

		internal long Domain_GetCorlib (long id) {
			return SendReceive (CommandSet.APPDOMAIN, (int)CmdAppDomain.GET_CORLIB, new PacketWriter ().WriteId (id)).ReadId ();
		}

		internal long Domain_CreateString (long id, string s) {
			return SendReceive (CommandSet.APPDOMAIN, (int)CmdAppDomain.CREATE_STRING, new PacketWriter ().WriteId (id).WriteString (s)).ReadId ();
		}

		internal long Domain_CreateBoxedValue (long id, long type_id, ValueImpl v) {
			return SendReceive (CommandSet.APPDOMAIN, (int)CmdAppDomain.CREATE_BOXED_VALUE, new PacketWriter ().WriteId (id).WriteId (type_id).WriteValue (v)).ReadId ();
		}

		/*
		 * METHOD
		 */

		internal string Method_GetName (long id) {
			return SendReceive (CommandSet.METHOD, (int)CmdMethod.GET_NAME, new PacketWriter ().WriteId (id)).ReadString ();
		}

		internal long Method_GetDeclaringType (long id) {
			return SendReceive (CommandSet.METHOD, (int)CmdMethod.GET_DECLARING_TYPE, new PacketWriter ().WriteId (id)).ReadId ();
		}

		internal DebugInfo Method_GetDebugInfo (long id) {
			var res = SendReceive (CommandSet.METHOD, (int)CmdMethod.GET_DEBUG_INFO, new PacketWriter ().WriteId (id));

			DebugInfo info = new DebugInfo ();
			info.max_il_offset = res.ReadInt ();

			SourceInfo[] sources = null;
			if (Version.AtLeast (2, 13)) {
				int n = res.ReadInt ();
				sources = new SourceInfo [n];
				for (int i = 0; i < n; ++i) {
					sources [i].source_file = res.ReadString ();
					if (Version.AtLeast (2, 14)) {
						sources [i].hash = new byte [16];
						for (int j = 0; j < 16; ++j)
							sources [i].hash [j] = (byte)res.ReadByte ();
					}
				}
			} else {
				sources = new SourceInfo [1];
				sources [0].source_file = res.ReadString ();
			}

			int n_il_offsets = res.ReadInt ();
			info.il_offsets = new int [n_il_offsets];
			info.line_numbers = new int [n_il_offsets];
			info.source_files = new SourceInfo [n_il_offsets];
			info.column_numbers = new int [n_il_offsets];
			info.end_line_numbers = new int [n_il_offsets];
			info.end_column_numbers = new int [n_il_offsets];
			for (int i = 0; i < n_il_offsets; ++i) {
				info.il_offsets [i] = res.ReadInt ();
				info.line_numbers [i] = res.ReadInt ();
				if (Version.AtLeast (2, 12)) {
					int idx = res.ReadInt ();
					info.source_files [i] = idx >= 0 ? sources [idx] : default (SourceInfo);
				} else {
					info.source_files [i] = sources [0];
				}
				if (Version.AtLeast (2, 19))
					info.column_numbers [i] = res.ReadInt ();
				else
					info.column_numbers [i] = 0;
				if (Version.AtLeast (2, 32)) {
					info.end_line_numbers [i] = res.ReadInt ();
					info.end_column_numbers [i] = res.ReadInt ();
				} else {
					info.end_column_numbers [i] = -1;
					info.end_column_numbers [i] = -1;
				}
			}

			return info;
		}

		internal ParamInfo Method_GetParamInfo (long id) {
			var res = SendReceive (CommandSet.METHOD, (int)CmdMethod.GET_PARAM_INFO, new PacketWriter ().WriteId (id));

			ParamInfo info = new ParamInfo ();
			info.call_conv = res.ReadInt ();
			info.param_count = res.ReadInt ();
			info.generic_param_count = res.ReadInt ();
			info.ret_type = res.ReadId ();
			info.param_types = new long [info.param_count];
			for (int i = 0; i < info.param_count; ++i)
				info.param_types [i] = res.ReadId ();
			info.param_names = new string [info.param_count];			
			for (int i = 0; i < info.param_count; ++i)
				info.param_names [i] = res.ReadString ();

			return info;
		}

		internal LocalsInfo Method_GetLocalsInfo (long id) {
			var res = SendReceive (CommandSet.METHOD, (int)CmdMethod.GET_LOCALS_INFO, new PacketWriter ().WriteId (id));

			LocalsInfo info = new LocalsInfo ();
			int nlocals = res.ReadInt ();
			info.types = new long [nlocals];
			for (int i = 0; i < nlocals; ++i)
				info.types [i] = res.ReadId ();
			info.names = new string [nlocals];
			for (int i = 0; i < nlocals; ++i)
				info.names [i] = res.ReadString ();
			info.live_range_start = new int [nlocals];
			info.live_range_end = new int [nlocals];
			for (int i = 0; i < nlocals; ++i) {
				info.live_range_start [i] = res.ReadInt ();
				info.live_range_end [i] = res.ReadInt ();
			}

			return info;
		}

		internal MethodInfo Method_GetInfo (long id) {
			var res = SendReceive (CommandSet.METHOD, (int)CmdMethod.GET_INFO, new PacketWriter ().WriteId (id));

			MethodInfo info = new MethodInfo ();
			info.attributes = res.ReadInt ();
			info.iattributes = res.ReadInt ();
			info.token = res.ReadInt ();
			if (Version.AtLeast (2, 12)) {
				int attrs = res.ReadByte ();
				if ((attrs & (1 << 0)) != 0)
					info.is_gmd = true;
				if ((attrs & (1 << 1)) != 0)
					info.is_generic_method = true;
				info.gmd = res.ReadId ();
				if (Version.AtLeast (2, 15)) {
					if (info.is_generic_method) {
						int n = res.ReadInt ();
						info.type_args = res.ReadIds (n);
					}
				}
			}
			return info;
		}

		internal MethodBodyInfo Method_GetBody (long id) {
			var res = SendReceive (CommandSet.METHOD, (int)CmdMethod.GET_BODY, new PacketWriter ().WriteId (id));

			MethodBodyInfo info = new MethodBodyInfo ();
			info.il = new byte [res.ReadInt ()];
			for (int i = 0; i < info.il.Length; ++i)
				info.il [i] = (byte)res.ReadByte ();

			if (Version.AtLeast (2, 18)) {
				info.clauses = new ExceptionClauseInfo [res.ReadInt ()];

				for (int i = 0; i < info.clauses.Length; ++i) {
					var clause = new ExceptionClauseInfo {
						flags = (ExceptionClauseFlags) res.ReadInt (),
						try_offset = res.ReadInt (),
						try_length = res.ReadInt (),
						handler_offset = res.ReadInt (),
						handler_length = res.ReadInt (),
					};

					if (clause.flags == ExceptionClauseFlags.None)
						clause.catch_type_id = res.ReadId ();
					else if (clause.flags == ExceptionClauseFlags.Filter)
						clause.filter_offset = res.ReadInt ();

					info.clauses [i] = clause;
				}
			} else
				info.clauses = new ExceptionClauseInfo [0];

			return info;
		}

		internal ResolvedToken Method_ResolveToken (long id, int token) {
			var res = SendReceive (CommandSet.METHOD, (int)CmdMethod.RESOLVE_TOKEN, new PacketWriter ().WriteId (id).WriteInt (token));

			TokenType type = (TokenType)res.ReadByte ();
			switch (type) {
			case TokenType.STRING:
				return new ResolvedToken () { Type = type, Str = res.ReadString () };
			case TokenType.TYPE:
			case TokenType.METHOD:
			case TokenType.FIELD:
				return new ResolvedToken () { Type = type, Id = res.ReadId () };
			case TokenType.UNKNOWN:
				return new ResolvedToken () { Type = type };
			default:
				throw new NotImplementedException ();
			}
		}

		internal CattrInfo[] Method_GetCustomAttributes (long id, long attr_type_id, bool inherit) {
			PacketReader r = SendReceive (CommandSet.METHOD, (int)CmdMethod.GET_CATTRS, new PacketWriter ().WriteId (id).WriteId (attr_type_id));
			return ReadCattrs (r);
		}

		internal long Method_MakeGenericMethod (long id, long[] args) {
			PacketReader r = SendReceive (CommandSet.METHOD, (int)CmdMethod.MAKE_GENERIC_METHOD, new PacketWriter ().WriteId (id).WriteInt (args.Length).WriteIds (args));
			return r.ReadId ();
		}

		/*
		 * THREAD
		 */

		internal string Thread_GetName (long id) {
			return SendReceive (CommandSet.THREAD, (int)CmdThread.GET_NAME, new PacketWriter ().WriteId (id)).ReadString ();
		}

		internal void Thread_GetFrameInfo (long id, int start_frame, int length, Action<FrameInfo[]> resultCallaback) {
			Send (CommandSet.THREAD, (int)CmdThread.GET_FRAME_INFO, new PacketWriter ().WriteId (id).WriteInt (start_frame).WriteInt (length), (res) => {
				int count = res.ReadInt ();
				var frames = new FrameInfo[count];
				for (int i = 0; i < count; ++i) {
					var f = new FrameInfo ();
					f.id = res.ReadInt ();
					f.method = res.ReadId ();
					f.il_offset = res.ReadInt ();
					f.flags = (StackFrameFlags)res.ReadByte ();
					frames [i] = f;
				}
				resultCallaback (frames);
			}, 1);
		}

		internal int Thread_GetState (long id) {
			return SendReceive (CommandSet.THREAD, (int)CmdThread.GET_STATE, new PacketWriter ().WriteId (id)).ReadInt ();
		}

		internal ThreadInfo Thread_GetInfo (long id) {
			PacketReader r = SendReceive (CommandSet.THREAD, (int)CmdThread.GET_INFO, new PacketWriter ().WriteId (id));

			ThreadInfo res = new ThreadInfo () { is_thread_pool = r.ReadByte () > 0 ? true : false };

			return res;
		}

		internal long Thread_GetId (long id) {
			return SendReceive (CommandSet.THREAD, (int)CmdThread.GET_ID, new PacketWriter ().WriteId (id)).ReadLong ();
		}

		internal long Thread_GetTID (long id) {
			return SendReceive (CommandSet.THREAD, (int)CmdThread.GET_TID, new PacketWriter ().WriteId (id)).ReadLong ();
		}

		internal void Thread_SetIP (long id, long method_id, long il_offset) {
			SendReceive (CommandSet.THREAD, (int)CmdThread.SET_IP, new PacketWriter ().WriteId (id).WriteId (method_id).WriteLong (il_offset));
		}

		/*
		 * MODULE
		 */

		internal ModuleInfo Module_GetInfo (long id) {
			PacketReader r = SendReceive (CommandSet.MODULE, (int)CmdModule.GET_INFO, new PacketWriter ().WriteId (id));
			ModuleInfo info = new ModuleInfo { Name = r.ReadString (), ScopeName = r.ReadString (), FQName = r.ReadString (), Guid = r.ReadString (), Assembly = r.ReadId () };
			return info;
		}

		/*
		 * ASSEMBLY
		 */

		internal string Assembly_GetLocation (long id) {
			return SendReceive (CommandSet.ASSEMBLY, (int)CmdAssembly.GET_LOCATION, new PacketWriter ().WriteId (id)).ReadString ();
		}

		internal long Assembly_GetEntryPoint (long id) {
			return SendReceive (CommandSet.ASSEMBLY, (int)CmdAssembly.GET_ENTRY_POINT, new PacketWriter ().WriteId (id)).ReadId ();
		}

		internal long Assembly_GetManifestModule (long id) {
			return SendReceive (CommandSet.ASSEMBLY, (int)CmdAssembly.GET_MANIFEST_MODULE, new PacketWriter ().WriteId (id)).ReadId ();
		}

		internal long Assembly_GetObject (long id) {
			return SendReceive (CommandSet.ASSEMBLY, (int)CmdAssembly.GET_OBJECT, new PacketWriter ().WriteId (id)).ReadId ();
		}

		internal long Assembly_GetType (long id, string name, bool ignoreCase) {
			return SendReceive (CommandSet.ASSEMBLY, (int)CmdAssembly.GET_TYPE, new PacketWriter ().WriteId (id).WriteString (name).WriteBool (ignoreCase)).ReadId ();
		}

		internal string Assembly_GetName (long id) {
			return SendReceive (CommandSet.ASSEMBLY, (int)CmdAssembly.GET_NAME, new PacketWriter ().WriteId (id)).ReadString ();
		}

		/*
		 * TYPE
		 */

		internal TypeInfo Type_GetInfo (long id) {
			PacketReader r = SendReceive (CommandSet.TYPE, (int)CmdType.GET_INFO, new PacketWriter ().WriteId (id));
			TypeInfo res = new TypeInfo ();

			res.ns = r.ReadString ();
			res.name = r.ReadString ();
			res.full_name = r.ReadString ();
			res.assembly = r.ReadId ();
			res.module = r.ReadId ();
			res.base_type = r.ReadId ();
			res.element_type = r.ReadId ();
			res.token = r.ReadInt ();
			res.rank = r.ReadByte ();
			res.attributes = r.ReadInt ();
			int b = r.ReadByte ();
			res.is_byref = (b & 1) != 0;
			res.is_pointer = (b & 2) != 0;
			res.is_primitive = (b & 4) != 0;
			res.is_valuetype = (b & 8) != 0;
			res.is_enum = (b & 16) != 0;
			res.is_gtd = (b & 32) != 0;
			res.is_generic_type = (b & 64) != 0;

			int nested_len = r.ReadInt ();
			res.nested = new long [nested_len];
			for (int i = 0; i < nested_len; ++i)
				res.nested [i] = r.ReadId ();

			if (Version.AtLeast (2, 12))
				res.gtd = r.ReadId ();
			if (Version.AtLeast (2, 15) && res.is_generic_type) {
				int n = r.ReadInt ();
				res.type_args = r.ReadIds (n);
			}

			return res;
		}

		internal long[] Type_GetMethods (long id) {
			PacketReader r = SendReceive (CommandSet.TYPE, (int)CmdType.GET_METHODS, new PacketWriter ().WriteId (id));

			int n = r.ReadInt ();
			long[] res = new long [n];
			for (int i = 0; i < n; ++i)
				res [i] = r.ReadId ();
			return res;
		}

		internal long[] Type_GetFields (long id, out string[] names, out long[] types, out int[] attrs) {
			PacketReader r = SendReceive (CommandSet.TYPE, (int)CmdType.GET_FIELDS, new PacketWriter ().WriteId (id));

			int n = r.ReadInt ();
			long[] res = new long [n];
			names = new string [n];
			types = new long [n];
			attrs = new int [n];
			for (int i = 0; i < n; ++i) {
				res [i] = r.ReadId ();
				names [i] = r.ReadString ();
				types [i] = r.ReadId ();
				attrs [i] = r.ReadInt ();
			}
			return res;
		}

		internal PropInfo[] Type_GetProperties (long id) {
			PacketReader r = SendReceive (CommandSet.TYPE, (int)CmdType.GET_PROPERTIES, new PacketWriter ().WriteId (id));

			int n = r.ReadInt ();
			PropInfo[] res = new PropInfo [n];
			for (int i = 0; i < n; ++i) {
				res [i] = new PropInfo ();
				res [i].id = r.ReadId ();
				res [i].name = r.ReadString ();
				res [i].get_method = r.ReadId ();
				res [i].set_method = r.ReadId ();
				res [i].attrs = r.ReadInt ();
			}

			return res;
		}

		internal long Type_GetObject (long id) {
			return SendReceive (CommandSet.TYPE, (int)CmdType.GET_OBJECT, new PacketWriter ().WriteId (id)).ReadId ();
		}

		internal ValueImpl[] Type_GetValues (long id, long[] fields, long thread_id) {
			int len = fields.Length;
			PacketReader r;
			if (thread_id != 0)
				r = SendReceive (CommandSet.TYPE, (int)CmdType.GET_VALUES_2, new PacketWriter ().WriteId (id).WriteId (thread_id).WriteInt (len).WriteIds (fields));
			else
				r = SendReceive (CommandSet.TYPE, (int)CmdType.GET_VALUES, new PacketWriter ().WriteId (id).WriteInt (len).WriteIds (fields));

			ValueImpl[] res = new ValueImpl [len];
			for (int i = 0; i < len; ++i)
				res [i] = r.ReadValue ();
			return res;
		}			

		internal void Type_SetValues (long id, long[] fields, ValueImpl[] values) {
			SendReceive (CommandSet.TYPE, (int)CmdType.SET_VALUES, new PacketWriter ().WriteId (id).WriteInt (fields.Length).WriteIds (fields).WriteValues (values));
		}

		internal string[] Type_GetSourceFiles (long id, bool return_full_paths) {
			var r = SendReceive (CommandSet.TYPE, return_full_paths ? (int)CmdType.GET_SOURCE_FILES_2 : (int)CmdType.GET_SOURCE_FILES, new PacketWriter ().WriteId (id));
			int len = r.ReadInt ();
			string[] res = new string [len];
			for (int i = 0; i < len; ++i)
				res [i] = r.ReadString ();
			return res;
		}

		internal bool Type_IsAssignableFrom (long id, long c_id) {
			return SendReceive (CommandSet.TYPE, (int)CmdType.IS_ASSIGNABLE_FROM, new PacketWriter ().WriteId (id).WriteId (c_id)).ReadByte () > 0;
		}

		internal CattrInfo[] Type_GetCustomAttributes (long id, long attr_type_id, bool inherit) {
			PacketReader r = SendReceive (CommandSet.TYPE, (int)CmdType.GET_CATTRS, new PacketWriter ().WriteId (id).WriteId (attr_type_id));
			return ReadCattrs (r);
		}

		internal CattrInfo[] Type_GetFieldCustomAttributes (long id, long field_id, long attr_type_id, bool inherit) {
			PacketReader r = SendReceive (CommandSet.TYPE, (int)CmdType.GET_FIELD_CATTRS, new PacketWriter ().WriteId (id).WriteId (field_id).WriteId (attr_type_id));
			return ReadCattrs (r);
		}

		internal CattrInfo[] Type_GetPropertyCustomAttributes (long id, long field_id, long attr_type_id, bool inherit) {
			PacketReader r = SendReceive (CommandSet.TYPE, (int)CmdType.GET_PROPERTY_CATTRS, new PacketWriter ().WriteId (id).WriteId (field_id).WriteId (attr_type_id));
			return ReadCattrs (r);
		}

		public long[] Type_GetMethodsByNameFlags (long id, string name, int flags, bool ignoreCase) {
			flags |= ignoreCase ? (int)BindingFlagsExtensions.BINDING_FLAGS_IGNORE_CASE : 0;
			PacketReader r = SendReceive (CommandSet.TYPE, (int)CmdType.CMD_TYPE_GET_METHODS_BY_NAME_FLAGS, new PacketWriter ().WriteId (id).WriteString (name).WriteInt (flags));
			int len = r.ReadInt ();
			long[] res = new long [len];
			for (int i = 0; i < len; ++i)
				res [i] = r.ReadId ();
			return res;
		}

		internal long[] Type_GetInterfaces (long id) {
			PacketReader r = SendReceive (CommandSet.TYPE, (int)CmdType.GET_INTERFACES, new PacketWriter ().WriteId (id));
			int len = r.ReadInt ();
			return r.ReadIds (len);
		}

		internal IfaceMapInfo[] Type_GetInterfaceMap (long id, long[] ids) {
			PacketReader r = SendReceive (CommandSet.TYPE, (int)CmdType.GET_INTERFACE_MAP, new PacketWriter ().WriteId (id).WriteInt (ids.Length).WriteIds (ids));
			var res = new IfaceMapInfo [ids.Length];
			for (int i = 0; i < ids.Length; ++i) {
				int n = r.ReadInt ();

				res [i].iface_id = ids [i];
				res [i].iface_methods = r.ReadIds (n);
				res [i].target_methods = r.ReadIds (n);
			}

			return res;
		}

		internal bool Type_IsInitialized (long id) {
			PacketReader r = SendReceive (CommandSet.TYPE, (int)CmdType.IS_INITIALIZED, new PacketWriter ().WriteId (id));
			return r.ReadInt () == 1;
		}

		internal long Type_CreateInstance (long id) {
			PacketReader r = SendReceive (CommandSet.TYPE, (int)CmdType.CREATE_INSTANCE, new PacketWriter ().WriteId (id));
			return r.ReadId ();
		}

		/*
		 * FIELD
		 */

		internal FieldMirrorInfo Field_GetInfo (long id) {
			PacketReader r = SendReceive (CommandSet.FIELD, (int)CmdField.GET_INFO, new PacketWriter ().WriteId (id));
			FieldMirrorInfo info = new FieldMirrorInfo { Name = r.ReadString (), Parent = r.ReadId (), TypeId = r.ReadId (), Attrs = r.ReadInt () };
			return info;
		}

		/*
		 * EVENTS
		 */

		internal int EnableEvent (EventType etype, SuspendPolicy suspend_policy, List<Modifier> mods) {
			var w = new PacketWriter ().WriteByte ((byte)etype).WriteByte ((byte)suspend_policy);
			if (mods != null) {
				if (mods.Count > 255)
					throw new NotImplementedException ();
				w.WriteByte ((byte)mods.Count);
				foreach (Modifier mod in mods) {
					if (mod is CountModifier) {
						w.WriteByte ((byte)ModifierKind.COUNT);
						w.WriteInt ((mod as CountModifier).Count);
					} else if (mod is LocationModifier) {
						w.WriteByte ((byte)ModifierKind.LOCATION_ONLY);
						w.WriteId ((mod as LocationModifier).Method);
						w.WriteLong ((mod as LocationModifier).Location);
					} else if (mod is StepModifier) {
						w.WriteByte ((byte)ModifierKind.STEP);
						w.WriteId ((mod as StepModifier).Thread);
						w.WriteInt ((mod as StepModifier).Size);
						w.WriteInt ((mod as StepModifier).Depth);
						if (Version.AtLeast (2, 16))
							w.WriteInt ((mod as StepModifier).Filter);
					} else if (mod is ThreadModifier) {
						w.WriteByte ((byte)ModifierKind.THREAD_ONLY);
						w.WriteId ((mod as ThreadModifier).Thread);
					} else if (mod is ExceptionModifier) {
						var em = mod as ExceptionModifier;
						w.WriteByte ((byte)ModifierKind.EXCEPTION_ONLY);
						w.WriteId (em.Type);
						if (Version.MajorVersion > 2 || Version.MinorVersion > 0) {
							/* This is only supported in protocol version 2.1 */
							w.WriteBool (em.Caught);
							w.WriteBool (em.Uncaught);
						} else if (!em.Caught || !em.Uncaught) {
							throw new NotSupportedException ("This request is not supported by the protocol version implemented by the debuggee.");
						}
						if (Version.MajorVersion > 2 || Version.MinorVersion > 24) {
							w.WriteBool (em.Subclasses);
						} else if (!em.Subclasses) {
							throw new NotSupportedException ("This request is not supported by the protocol version implemented by the debuggee.");
						}
					} else if (mod is AssemblyModifier) {
						w.WriteByte ((byte)ModifierKind.ASSEMBLY_ONLY);
						var amod = (mod as AssemblyModifier);
						w.WriteInt (amod.Assemblies.Length);
						foreach (var id in amod.Assemblies)
							w.WriteId (id);
					} else if (mod is SourceFileModifier) {
						w.WriteByte ((byte)ModifierKind.SOURCE_FILE_ONLY);
						var smod = (mod as SourceFileModifier);
						w.WriteInt (smod.SourceFiles.Length);
						foreach (var s in smod.SourceFiles)
							w.WriteString (s);
					} else if (mod is TypeNameModifier) {
						w.WriteByte ((byte)ModifierKind.TYPE_NAME_ONLY);
						var tmod = (mod as TypeNameModifier);
						w.WriteInt (tmod.TypeNames.Length);
						foreach (var s in tmod.TypeNames)
							w.WriteString (s);
					} else {
						throw new NotImplementedException ();
					}
				}
			} else {
				w.WriteByte (0);
			}
			return SendReceive (CommandSet.EVENT_REQUEST, (int)CmdEventRequest.SET, w).ReadInt ();
		}

		internal void ClearEventRequest (EventType etype, int req_id) {
			SendReceive (CommandSet.EVENT_REQUEST, (int)CmdEventRequest.CLEAR, new PacketWriter ().WriteByte ((byte)etype).WriteInt (req_id));
		}			

		internal void ClearAllBreakpoints () {
			SendReceive (CommandSet.EVENT_REQUEST, (int)CmdEventRequest.CLEAR_ALL_BREAKPOINTS, new PacketWriter ());
		}
			
		/*
		 * STACK FRAME
		 */
		internal ValueImpl StackFrame_GetThis (long thread_id, long id) {
			PacketReader r = SendReceive (CommandSet.STACK_FRAME, (int)CmdStackFrame.GET_THIS, new PacketWriter ().WriteId (thread_id).WriteId (id));
			return r.ReadValue ();
		}

		internal ValueImpl[] StackFrame_GetValues (long thread_id, long id, int[] pos) {
			/* pos < 0 -> argument at pos (-pos) - 1 */
			/* pos >= 0 -> local at pos */
			int len = pos.Length;
			PacketReader r = SendReceive (CommandSet.STACK_FRAME, (int)CmdStackFrame.GET_VALUES, new PacketWriter ().WriteId (thread_id).WriteId (id).WriteInt (len).WriteInts (pos));

			ValueImpl[] res = new ValueImpl [len];
			for (int i = 0; i < len; ++i)
				res [i] = r.ReadValue ();
			return res;
		}

		internal void StackFrame_SetValues (long thread_id, long id, int[] pos, ValueImpl[] values) {
			/* pos < 0 -> argument at pos (-pos) - 1 */
			/* pos >= 0 -> local at pos */
			int len = pos.Length;
			SendReceive (CommandSet.STACK_FRAME, (int)CmdStackFrame.SET_VALUES, new PacketWriter ().WriteId (thread_id).WriteId (id).WriteInt (len).WriteInts (pos).WriteValues (values));
		}

		internal long StackFrame_GetDomain (long thread_id, long id) {
			return SendReceive (CommandSet.STACK_FRAME, (int)CmdStackFrame.GET_DOMAIN, new PacketWriter ().WriteId (thread_id).WriteId (id)).ReadId ();
		}

		/*
		 * ARRAYS
		 */
		internal int[] Array_GetLength (long id, out int rank, out int[] lower_bounds) {
			var r = SendReceive (CommandSet.ARRAY_REF, (int)CmdArrayRef.GET_LENGTH, new PacketWriter ().WriteId (id));
			rank = r.ReadInt ();
			int[] res = new int [rank];
			lower_bounds = new int [rank];
			for (int i = 0; i < rank; ++i) {
				res [i] = r.ReadInt ();
				lower_bounds [i] = r.ReadInt ();
			}
			return res;
		}

		internal ValueImpl[] Array_GetValues (long id, int index, int len) {
			var r = SendReceive (CommandSet.ARRAY_REF, (int)CmdArrayRef.GET_VALUES, new PacketWriter ().WriteId (id).WriteInt (index).WriteInt (len));
			ValueImpl[] res = new ValueImpl [len];
			for (int i = 0; i < len; ++i)
				res [i] = r.ReadValue ();
			return res;
		}

		internal void Array_SetValues (long id, int index, ValueImpl[] values) {
			SendReceive (CommandSet.ARRAY_REF, (int)CmdArrayRef.SET_VALUES, new PacketWriter ().WriteId (id).WriteInt (index).WriteInt (values.Length).WriteValues (values));
		}

		/*
		 * STRINGS
		 */
		internal string String_GetValue (long id) {
			return SendReceive (CommandSet.STRING_REF, (int)CmdStringRef.GET_VALUE, new PacketWriter ().WriteId (id)).ReadString ();
		}			

		internal int String_GetLength (long id) {
			return (int)SendReceive (CommandSet.STRING_REF, (int)CmdStringRef.GET_LENGTH, new PacketWriter ().WriteId (id)).ReadLong ();
		}			

		internal char[] String_GetChars (long id, int index, int length) {
			var r = SendReceive (CommandSet.STRING_REF, (int)CmdStringRef.GET_CHARS, new PacketWriter ().WriteId (id).WriteLong (index).WriteLong (length));
			var res = new char [length];
			for (int i = 0; i < length; ++i)
				res [i] = (char)r.ReadShort ();
			return res;
		}			

		/*
		 * OBJECTS
		 */
		internal long Object_GetType (long id) {
			return SendReceive (CommandSet.OBJECT_REF, (int)CmdObjectRef.GET_TYPE, new PacketWriter ().WriteId (id)).ReadId ();
		}			

		internal long Object_GetDomain (long id) {
			return SendReceive (CommandSet.OBJECT_REF, (int)CmdObjectRef.GET_DOMAIN, new PacketWriter ().WriteId (id)).ReadId ();
		}			

		internal ValueImpl[] Object_GetValues (long id, long[] fields) {
			int len = fields.Length;
			PacketReader r = SendReceive (CommandSet.OBJECT_REF, (int)CmdObjectRef.GET_VALUES, new PacketWriter ().WriteId (id).WriteInt (len).WriteIds (fields));

			ValueImpl[] res = new ValueImpl [len];
			for (int i = 0; i < len; ++i)
				res [i] = r.ReadValue ();
			return res;
		}

		internal void Object_SetValues (long id, long[] fields, ValueImpl[] values) {
			SendReceive (CommandSet.OBJECT_REF, (int)CmdObjectRef.SET_VALUES, new PacketWriter ().WriteId (id).WriteInt (fields.Length).WriteIds (fields).WriteValues (values));
		}

		internal bool Object_IsCollected (long id) {
			return SendReceive (CommandSet.OBJECT_REF, (int)CmdObjectRef.IS_COLLECTED, new PacketWriter ().WriteId (id)).ReadInt () == 1;
		}			

		internal long Object_GetAddress (long id) {
			return SendReceive (CommandSet.OBJECT_REF, (int)CmdObjectRef.GET_ADDRESS, new PacketWriter ().WriteId (id)).ReadLong ();
		}			

		internal ObjectRefInfo Object_GetInfo (long id) {
			ObjectRefInfo res = new ObjectRefInfo ();
			PacketReader r = SendReceive (CommandSet.OBJECT_REF, (int)CmdObjectRef.GET_INFO, new PacketWriter ().WriteId (id));

			res.type_id = r.ReadId ();
			res.domain_id = r.ReadId ();
			return res;
		}

		public void ForceDisconnect ()
		{
			closed = true;
			disconnected = true;
			DisconnectedEvent.Set ();
			TransportClose ();
		}
	}
	
	class TcpConnection : Connection
	{
		Socket socket;
		
		internal TcpConnection (Socket socket)
		{
			this.socket = socket;
			//socket.SetSocketOption (SocketOptionLevel.IP, SocketOptionName.NoDelay, 1);
		}
		
		internal EndPoint EndPoint {
			get {
				return socket.RemoteEndPoint;
			}
		}
		
		protected override int TransportSend (byte[] buf, int buf_offset, int len)
		{
			return socket.Send (buf, buf_offset, len, SocketFlags.None);
		}
		
		protected override int TransportReceive (byte[] buf, int buf_offset, int len)
		{
			return socket.Receive (buf, buf_offset, len, SocketFlags.None);
		}
		
		protected override void TransportSetTimeouts (int send_timeout, int receive_timeout)
		{
			socket.SendTimeout = send_timeout;
			socket.ReceiveTimeout = receive_timeout;
		}
		
		protected override void TransportClose ()
		{
			socket.Close ();
		}
	}

	/* This is the interface exposed by the debugger towards the debugger agent */
	interface IEventHandler
	{
		void Events (SuspendPolicy suspend_policy, EventInfo[] events);

		void VMDisconnect (int req_id, long thread_id, string vm_uri);
	}
}
