//
// ReflectionReader.cs
//
// Author:
//   Jb Evain (jbevain@gmail.com)
//
// (C) 2005 - 2007 Jb Evain
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

namespace Mono.Cecil {

	using System;
	using System.IO;
	using System.Text;

	using Mono.Cecil.Binary;
	using Mono.Cecil.Cil;
	using Mono.Cecil.Metadata;
	using Mono.Cecil.Signatures;

	internal abstract class ReflectionReader : BaseReflectionReader {

		ModuleDefinition m_module;
		ImageReader m_reader;
		SecurityDeclarationReader m_secReader;
		protected MetadataTableReader m_tableReader;
		protected MetadataRoot m_root;
		protected TablesHeap m_tHeap;
		protected bool m_checkDeleted;

		protected TypeDefinition [] m_typeDefs;
		protected TypeReference [] m_typeRefs;
		protected TypeReference [] m_typeSpecs;
		protected MethodDefinition [] m_meths;
		protected FieldDefinition [] m_fields;
		protected EventDefinition [] m_events;
		protected PropertyDefinition [] m_properties;
		protected MemberReference [] m_memberRefs;
		protected ParameterDefinition [] m_parameters;
		protected GenericParameter [] m_genericParameters;
		protected GenericInstanceMethod [] m_methodSpecs;

		bool m_isCorlib;
		AssemblyNameReference m_corlib;

		protected SignatureReader m_sigReader;
		protected CodeReader m_codeReader;
		protected ISymbolReader m_symbolReader;

		internal AssemblyNameReference Corlib {
			get {
				if (m_corlib != null)
					return m_corlib;

				foreach (AssemblyNameReference ar in m_module.AssemblyReferences) {
					if (ar.Name == Constants.Corlib) {
						m_corlib = ar;
						return m_corlib;
					}
				}

				return null;
			}
		}

		public ModuleDefinition Module {
			get { return m_module; }
		}

		public SignatureReader SigReader {
			get { return m_sigReader; }
		}

		public MetadataTableReader TableReader {
			get { return m_tableReader; }
		}

		public CodeReader Code {
			get { return m_codeReader; }
		}

		public ISymbolReader SymbolReader {
			get { return m_symbolReader; }
			set { m_symbolReader = value; }
		}

		public MetadataRoot MetadataRoot {
			get { return m_root; }
		}

		public ReflectionReader (ModuleDefinition module)
		{
			m_module = module;
			m_reader = m_module.ImageReader;
			m_root = m_module.Image.MetadataRoot;
			m_tHeap = m_root.Streams.TablesHeap;
			m_checkDeleted = (m_tHeap.HeapSizes & 0x80) != 0;
			if (m_reader != null)
				m_tableReader = m_reader.MetadataReader.TableReader;
			m_codeReader = new CodeReader (this);
			m_sigReader = new SignatureReader (m_root, this);
			m_isCorlib = module.Assembly.Name.Name == Constants.Corlib;
		}

		public TypeDefinition GetTypeDefAt (uint rid)
		{
			if (rid > m_typeDefs.Length)
				return null;

			return m_typeDefs [rid - 1];
		}

		public TypeReference GetTypeRefAt (uint rid)
		{
			if (rid > m_typeRefs.Length)
				return null;

			return m_typeRefs [rid - 1];
		}

		public TypeReference GetTypeSpecAt (uint rid, GenericContext context)
		{
			if (rid > m_typeSpecs.Length)
				return null;

			int index = (int) rid - 1;

			TypeSpecTable tsTable = m_tableReader.GetTypeSpecTable ();
			TypeSpecRow tsRow = tsTable [index];
			TypeSpec ts = m_sigReader.GetTypeSpec (tsRow.Signature);

			// don't cache generic instances
			if (ts.Type.ElementType == ElementType.GenericInst)
				return CreateTypeSpecFromSig (ts, index, context);

			TypeReference tspec = m_typeSpecs [index];
			if (tspec != null)
				return tspec;

			tspec = CreateTypeSpecFromSig (ts, index, context);
			m_typeSpecs [index] = tspec;

			return tspec;
		}

		TypeReference CreateTypeSpecFromSig (TypeSpec ts, int index, GenericContext context)
		{
			TypeReference tspec = GetTypeRefFromSig (ts.Type, context);
			tspec = GetModifierType (ts.CustomMods, tspec);
			tspec.MetadataToken = MetadataToken.FromMetadataRow (TokenType.TypeSpec, index);
			return tspec;
		}

		public FieldDefinition GetFieldDefAt (uint rid)
		{
			if (rid > m_fields.Length)
				return null;

			return m_fields [rid - 1];
		}

		public MethodDefinition GetMethodDefAt (uint rid)
		{
			if (rid > m_meths.Length)
				return null;

			return m_meths [rid - 1];
		}

		protected bool IsDeleted (IMemberDefinition member)
		{
			if (!m_checkDeleted)
				return false;

			if (!member.IsSpecialName || !member.IsRuntimeSpecialName)
				return false;

			return member.Name.StartsWith (Constants.Deleted);
		}

		public MemberReference GetMemberRefAt (uint rid, GenericContext context)
		{
			if (rid > m_memberRefs.Length)
				return null;

			int index = (int) rid - 1;
			MemberReference member = m_memberRefs [index];
			if (member != null)
				return member;

			MemberRefTable mrTable = m_tableReader.GetMemberRefTable ();
			MemberRefRow mrefRow = mrTable [index];

			Signature sig = m_sigReader.GetMemberRefSig (mrefRow.Class.TokenType, mrefRow.Signature);
			switch (mrefRow.Class.TokenType) {
			case TokenType.TypeDef :
			case TokenType.TypeRef :
			case TokenType.TypeSpec :
				TypeReference declaringType = GetTypeDefOrRef (mrefRow.Class, context);
				GenericContext nc = context.Clone ();

				if (declaringType is GenericInstanceType) {
					TypeReference ct = declaringType;
					while (ct is GenericInstanceType)
						ct = (ct as GenericInstanceType).ElementType;

					nc.Type = ct;
				}

				if (sig is FieldSig) {
					FieldSig fs = sig as FieldSig;
					TypeReference fieldType = GetTypeRefFromSig (fs.Type, nc);
					fieldType = GetModifierType (fs.CustomMods, fieldType);

					member = new FieldReference (
						m_root.Streams.StringsHeap [mrefRow.Name],
						declaringType,
						fieldType);
				} else {
					string name = m_root.Streams.StringsHeap [mrefRow.Name];
					MethodSig ms = (MethodSig) sig;

					member = CreateMethodReferenceFromSig (ms, name, declaringType, nc);
				}
				break;
			case TokenType.Method :
				// really not sure about this
				MethodDefinition methdef = GetMethodDefAt (mrefRow.Class.RID);

				member = CreateMethodReferenceFromSig ((MethodSig) sig, methdef.Name, methdef.DeclaringType, new GenericContext ());
				break;
			case TokenType.ModuleRef :
				break; // TODO, implement that, or not
			}

			member.MetadataToken = MetadataToken.FromMetadataRow (TokenType.MemberRef, index);
			m_module.MemberReferences.Add (member);
			m_memberRefs [index] = member;

			return member;
		}

		MethodReference CreateMethodReferenceFromSig (MethodSig ms, string name, TypeReference declaringType, GenericContext context)
		{
			MethodReference methref = new MethodReference (
				name, ms.HasThis, ms.ExplicitThis, ms.MethCallConv);
			methref.DeclaringType = declaringType;

			if (ms is MethodDefSig) {
				int arity = (ms as MethodDefSig).GenericParameterCount;
				for (int i = 0; i < arity; i++)
					methref.GenericParameters.Add (new GenericParameter (i, methref));
			}

			if (methref.GenericParameters.Count > 0)
				context.Method = methref;

			methref.ReturnType = GetMethodReturnType (ms, context);

			methref.ReturnType.Method = methref;
			for (int j = 0; j < ms.ParamCount; j++) {
				Param p = ms.Parameters [j];
				ParameterDefinition pdef = BuildParameterDefinition (j, p, context);
				pdef.Method = methref;
				methref.Parameters.Add (pdef);
			}

			CreateSentinelIfNeeded (methref, ms);

			return methref;
		}

		public static void CreateSentinelIfNeeded (IMethodSignature meth, MethodSig signature)
		{
			MethodDefSig sig = signature as MethodDefSig;
			if (sig == null)
				return;

			int sentinel = sig.Sentinel;

			if (sig.Sentinel < 0 || sig.Sentinel >= meth.Parameters.Count)
				return;

			ParameterDefinition param = meth.Parameters [sentinel];
			param.ParameterType = new SentinelType (param.ParameterType);
		}

		public PropertyDefinition GetPropertyDefAt (uint rid)
		{
			if (rid > m_properties.Length)
				return null;

			return m_properties [rid - 1];
		}

		public EventDefinition GetEventDefAt (uint rid)
		{
			if (rid > m_events.Length)
				return null;

			return m_events [rid - 1];
		}

		public ParameterDefinition GetParamDefAt (uint rid)
		{
			if (rid > m_parameters.Length)
				return null;

			return m_parameters [rid - 1];
		}

		public GenericParameter GetGenericParameterAt (uint rid)
		{
			if (rid > m_genericParameters.Length)
				return null;

			return m_genericParameters [rid - 1];
		}

		public GenericInstanceMethod GetMethodSpecAt (uint rid, GenericContext context)
		{
			if (rid > m_methodSpecs.Length)
				return null;

			int index = (int) rid - 1;
			GenericInstanceMethod gim = m_methodSpecs [index];
			if (gim != null)
				return gim;

			MethodSpecTable msTable = m_tableReader.GetMethodSpecTable ();
			MethodSpecRow msRow = msTable [index];

			MethodSpec sig = m_sigReader.GetMethodSpec (msRow.Instantiation);

			MethodReference meth;
			if (msRow.Method.TokenType == TokenType.Method)
				meth = GetMethodDefAt (msRow.Method.RID);
			else if (msRow.Method.TokenType == TokenType.MemberRef)
				meth = (MethodReference) GetMemberRefAt (msRow.Method.RID, context);
			else
				throw new ReflectionException ("Unknown method type for method spec");

			gim = new GenericInstanceMethod (meth);
			context.CheckProvider (meth, sig.Signature.Arity);
			foreach (GenericArg arg in sig.Signature.Types)
				gim.GenericArguments.Add (GetGenericArg (arg, context));

			m_methodSpecs [index] = gim;

			return gim;
		}

		public TypeReference GetTypeDefOrRef (MetadataToken token, GenericContext context)
		{
			if (token.RID == 0)
				return null;

			switch (token.TokenType) {
			case TokenType.TypeDef :
				return GetTypeDefAt (token.RID);
			case TokenType.TypeRef :
				return GetTypeRefAt (token.RID);
			case TokenType.TypeSpec :
				return GetTypeSpecAt (token.RID, context);
			default :
				return null;
			}
		}

		public TypeReference SearchCoreType (string fullName)
		{
			if (m_isCorlib)
				return m_module.Types [fullName];

			TypeReference coreType =  m_module.TypeReferences [fullName];
			if (coreType == null) {
				string [] parts = fullName.Split ('.');
				if (parts.Length != 2)
					throw new ReflectionException ("Unvalid core type name");
				coreType = new TypeReference (parts [1], parts [0], Corlib);
				m_module.TypeReferences.Add (coreType);
			}
			if (!coreType.IsValueType) {
				switch (coreType.FullName) {
				case Constants.Boolean :
				case Constants.Char :
				case Constants.Single :
				case Constants.Double :
				case Constants.SByte :
				case Constants.Byte :
				case Constants.Int16 :
				case Constants.UInt16 :
				case Constants.Int32 :
				case Constants.UInt32 :
				case Constants.Int64 :
				case Constants.UInt64 :
				case Constants.IntPtr :
				case Constants.UIntPtr :
					coreType.IsValueType = true;
					break;
				}
			}
			return coreType;
		}

		public IMetadataTokenProvider LookupByToken (MetadataToken token)
		{
			switch (token.TokenType) {
			case TokenType.TypeDef :
				return GetTypeDefAt (token.RID);
			case TokenType.TypeRef :
				return GetTypeRefAt (token.RID);
			case TokenType.Method :
				return GetMethodDefAt (token.RID);
			case TokenType.Field :
				return GetFieldDefAt (token.RID);
			case TokenType.Event :
				return GetEventDefAt (token.RID);
			case TokenType.Property :
				return GetPropertyDefAt (token.RID);
			case TokenType.Param :
				return GetParamDefAt (token.RID);
			default :
				throw new NotSupportedException ("Lookup is not allowed on this kind of token");
			}
		}

		public CustomAttribute GetCustomAttribute (MethodReference ctor, byte [] data, bool resolve)
		{
			CustomAttrib sig = m_sigReader.GetCustomAttrib (data, ctor, resolve);
			return BuildCustomAttribute (ctor, data, sig);
		}

		public CustomAttribute GetCustomAttribute (MethodReference ctor, byte [] data)
		{
			return GetCustomAttribute (ctor, data, false);
		}

		public override void VisitModuleDefinition (ModuleDefinition mod)
		{
			VisitTypeDefinitionCollection (mod.Types);
		}

		public override void VisitTypeDefinitionCollection (TypeDefinitionCollection types)
		{
			// type def reading
			TypeDefTable typesTable = m_tableReader.GetTypeDefTable ();
			m_typeDefs = new TypeDefinition [typesTable.Rows.Count];
			for (int i = 0; i < typesTable.Rows.Count; i++) {
				TypeDefRow type = typesTable [i];
				TypeDefinition t = new TypeDefinition (
					m_root.Streams.StringsHeap [type.Name],
					m_root.Streams.StringsHeap [type.Namespace],
					type.Flags);
				t.MetadataToken = MetadataToken.FromMetadataRow (TokenType.TypeDef, i);

				m_typeDefs [i] = t;
			}

			// nested types
			if (m_tHeap.HasTable (NestedClassTable.RId)) {
				NestedClassTable nested = m_tableReader.GetNestedClassTable ();
				for (int i = 0; i < nested.Rows.Count; i++) {
					NestedClassRow row = nested [i];

					TypeDefinition parent = GetTypeDefAt (row.EnclosingClass);
					TypeDefinition child = GetTypeDefAt (row.NestedClass);

					if (!IsDeleted (child))
						parent.NestedTypes.Add (child);
				}
			}

			foreach (TypeDefinition type in m_typeDefs)
				if (!IsDeleted (type))
					types.Add (type);

			// type ref reading
			if (m_tHeap.HasTable (TypeRefTable.RId)) {
				TypeRefTable typesRef = m_tableReader.GetTypeRefTable ();

				m_typeRefs = new TypeReference [typesRef.Rows.Count];

				for (int i = 0; i < typesRef.Rows.Count; i++)
					AddTypeRef (typesRef, i);
			} else
				m_typeRefs = new TypeReference [0];

			ReadTypeSpecs ();
			ReadMethodSpecs ();

			ReadMethods ();
			ReadGenericParameters ();

			// set base types
			for (int i = 0; i < typesTable.Rows.Count; i++) {
				TypeDefRow type = typesTable [i];
				TypeDefinition child = m_typeDefs [i];
				child.BaseType = GetTypeDefOrRef (type.Extends, new GenericContext (child));
			}

			CompleteMethods ();
			ReadAllFields ();
			ReadMemberReferences ();
		}

		void AddTypeRef (TypeRefTable typesRef, int i)
		{
			if (i >= typesRef.Rows.Count)
				return;

			// Check if index has been already added.
			if (m_typeRefs [i] != null)
				return;

			TypeRefRow type = typesRef [i];
			IMetadataScope scope = null;
			TypeReference parent = null;

			if (type.ResolutionScope.RID != 0) {
				int rid = (int) type.ResolutionScope.RID - 1;
				switch (type.ResolutionScope.TokenType) {
				case TokenType.AssemblyRef:
					scope = m_module.AssemblyReferences [rid];
					break;
				case TokenType.ModuleRef:
					scope = m_module.ModuleReferences [rid];
					break;
				case TokenType.Module:
					scope = m_module.Assembly.Modules [rid];
					break;
				case TokenType.TypeRef:
					AddTypeRef (typesRef, rid);
					parent = GetTypeRefAt (type.ResolutionScope.RID);
					if (parent != null)
						scope = parent.Scope;
					break;
				}
			}

			TypeReference t = new TypeReference (
				m_root.Streams.StringsHeap [type.Name],
				m_root.Streams.StringsHeap [type.Namespace],
				scope);
			t.MetadataToken = MetadataToken.FromMetadataRow (TokenType.TypeRef, i);

			if (parent != null)
				t.DeclaringType = parent;

			m_typeRefs [i] = t;
			m_module.TypeReferences.Add (t);
		}

		void ReadTypeSpecs ()
		{
			if (!m_tHeap.HasTable (TypeSpecTable.RId))
				return;

			TypeSpecTable tsTable = m_tableReader.GetTypeSpecTable ();
			m_typeSpecs = new TypeReference [tsTable.Rows.Count];
		}

		void ReadMethodSpecs ()
		{
			if (!m_tHeap.HasTable (MethodSpecTable.RId))
				return;

			MethodSpecTable msTable = m_tableReader.GetMethodSpecTable ();
			m_methodSpecs = new GenericInstanceMethod [msTable.Rows.Count];
		}

		void ReadGenericParameters ()
		{
			if (!m_tHeap.HasTable (GenericParamTable.RId))
				return;

			GenericParamTable gpTable = m_tableReader.GetGenericParamTable ();
			m_genericParameters = new GenericParameter [gpTable.Rows.Count];
			for (int i = 0; i < gpTable.Rows.Count; i++) {
				GenericParamRow gpRow = gpTable [i];
				IGenericParameterProvider owner;
				if (gpRow.Owner.TokenType == TokenType.Method)
					owner = GetMethodDefAt (gpRow.Owner.RID);
				else if (gpRow.Owner.TokenType == TokenType.TypeDef)
					owner = GetTypeDefAt (gpRow.Owner.RID);
				else
					throw new ReflectionException ("Unknown owner type for generic parameter");

				GenericParameter gp = new GenericParameter (gpRow.Number, owner);
				gp.Attributes = gpRow.Flags;
				gp.Name = MetadataRoot.Streams.StringsHeap [gpRow.Name];
				gp.MetadataToken = MetadataToken.FromMetadataRow (TokenType.GenericParam, i);

				owner.GenericParameters.Add (gp);
				m_genericParameters [i] = gp;
			}
		}

		void ReadAllFields ()
		{
			TypeDefTable tdefTable = m_tableReader.GetTypeDefTable ();

			if (!m_tHeap.HasTable (FieldTable.RId)) {
				m_fields = new FieldDefinition [0];
				return;
			}

			FieldTable fldTable = m_tableReader.GetFieldTable ();
			m_fields = new FieldDefinition [fldTable.Rows.Count];

			for (int i = 0; i < m_typeDefs.Length; i++) {
				TypeDefinition dec = m_typeDefs [i];
				GenericContext context = new GenericContext (dec);

				int index = i, next;

				if (index == tdefTable.Rows.Count - 1)
					next = fldTable.Rows.Count + 1;
				else
					next = (int) (tdefTable [index + 1]).FieldList;

				for (int j = (int) tdefTable [index].FieldList; j < next; j++) {
					FieldRow frow = fldTable [j - 1];
					FieldSig fsig = m_sigReader.GetFieldSig (frow.Signature);

					FieldDefinition fdef = new FieldDefinition (
						m_root.Streams.StringsHeap [frow.Name],
						GetTypeRefFromSig (fsig.Type, context), frow.Flags);
					fdef.MetadataToken = MetadataToken.FromMetadataRow (TokenType.Field, j - 1);

					if (fsig.CustomMods.Length > 0)
						fdef.FieldType = GetModifierType (fsig.CustomMods, fdef.FieldType);

					if (!IsDeleted (fdef))
						dec.Fields.Add (fdef);

					m_fields [j - 1] = fdef;
				}
			}
		}

		void ReadMethods ()
		{
			if (!m_tHeap.HasTable (MethodTable.RId)) {
				m_meths = new MethodDefinition [0];
				return;
			}

			MethodTable mTable = m_tableReader.GetMethodTable ();
			m_meths = new MethodDefinition [mTable.Rows.Count];
			for (int i = 0; i < mTable.Rows.Count; i++) {
				MethodRow mRow = mTable [i];
				MethodDefinition meth = new MethodDefinition (
					m_root.Streams.StringsHeap [mRow.Name],
					mRow.Flags);
				meth.RVA = mRow.RVA;
				meth.ImplAttributes = mRow.ImplFlags;

				meth.MetadataToken = MetadataToken.FromMetadataRow (TokenType.Method, i);

				m_meths [i] = meth;
			}
		}

		void CompleteMethods ()
		{
			TypeDefTable tdefTable = m_tableReader.GetTypeDefTable ();

			if (!m_tHeap.HasTable (MethodTable.RId)) {
				m_meths = new MethodDefinition [0];
				return;
			}

			MethodTable methTable = m_tableReader.GetMethodTable ();
			ParamTable paramTable = m_tableReader.GetParamTable ();
			if (!m_tHeap.HasTable (ParamTable.RId))
				m_parameters = new ParameterDefinition [0];
			else
				m_parameters = new ParameterDefinition [paramTable.Rows.Count];

			for (int i = 0; i < m_typeDefs.Length; i++) {
				TypeDefinition dec = m_typeDefs [i];

				int index = i, next;

				if (index == tdefTable.Rows.Count - 1)
					next = methTable.Rows.Count + 1;
				else
					next = (int) (tdefTable [index + 1]).MethodList;

				for (int j = (int) tdefTable [index].MethodList; j < next; j++) {
					MethodRow methRow = methTable [j - 1];
					MethodDefinition mdef = m_meths [j - 1];

					if (!IsDeleted (mdef)) {
						if (mdef.IsConstructor)
							dec.Constructors.Add (mdef);
						else
							dec.Methods.Add (mdef);
					}

					GenericContext context = new GenericContext (mdef);

					MethodDefSig msig = m_sigReader.GetMethodDefSig (methRow.Signature);
					mdef.HasThis = msig.HasThis;
					mdef.ExplicitThis = msig.ExplicitThis;
					mdef.CallingConvention = msig.MethCallConv;

					int prms;
					if (j == methTable.Rows.Count)
						prms = m_parameters.Length + 1;
					else
						prms = (int) (methTable [j]).ParamList;

					ParameterDefinition retparam = null;

					//TODO: optimize this
					int start = (int) methRow.ParamList - 1;

					if (paramTable != null && start < prms - 1) {

						ParamRow pRetRow = paramTable [start];

						if (pRetRow != null && pRetRow.Sequence == 0) { // ret type

							retparam = new ParameterDefinition (
								m_root.Streams.StringsHeap [pRetRow.Name],
								0,
								pRetRow.Flags,
								null);

							retparam.Method = mdef;
							m_parameters [start] = retparam;
							start++;
						}
					}

					for (int k = 0; k < msig.ParamCount; k++) {

						int pointer = start + k;

						ParamRow pRow = null;

						if (paramTable != null && pointer < prms - 1)
							pRow = paramTable [pointer];

						Param psig = msig.Parameters [k];

						ParameterDefinition pdef;
						if (pRow != null) {
							pdef = BuildParameterDefinition (
								m_root.Streams.StringsHeap [pRow.Name],
								pRow.Sequence, pRow.Flags, psig, context);
							pdef.MetadataToken = MetadataToken.FromMetadataRow (TokenType.Param, pointer);
							m_parameters [pointer] = pdef;
						} else
							pdef = BuildParameterDefinition (k + 1, psig, context);

						pdef.Method = mdef;
						mdef.Parameters.Add (pdef);
					}

					mdef.ReturnType = GetMethodReturnType (msig, context);
					MethodReturnType mrt = mdef.ReturnType;
					mrt.Method = mdef;
					if (retparam != null) {
						mrt.Parameter = retparam;
						mrt.Parameter.ParameterType = mrt.ReturnType;
					}
				}
			}

			uint eprid = CodeReader.GetRid ((int) m_reader.Image.CLIHeader.EntryPointToken);
			if (eprid > 0 && eprid <= m_meths.Length)
				m_module.Assembly.EntryPoint = GetMethodDefAt (eprid);
		}

		void ReadMemberReferences ()
		{
			if (!m_tHeap.HasTable (MemberRefTable.RId))
				return;

			MemberRefTable mrefTable = m_tableReader.GetMemberRefTable ();
			m_memberRefs = new MemberReference [mrefTable.Rows.Count];
		}

		public override void VisitExternTypeCollection (ExternTypeCollection externs)
		{
			ExternTypeCollection ext = externs;

			if (!m_tHeap.HasTable (ExportedTypeTable.RId))
				return;

			ExportedTypeTable etTable = m_tableReader.GetExportedTypeTable ();
			TypeReference [] buffer = new TypeReference [etTable.Rows.Count];

			for (int i = 0; i < etTable.Rows.Count; i++) {
				ExportedTypeRow etRow = etTable [i];

				buffer [i] = new TypeDefinition (
					m_root.Streams.StringsHeap [etRow.TypeName],
					m_root.Streams.StringsHeap [etRow.TypeNamespace],
					etRow.Flags);
				buffer [i].AttachToScope (GetExportedTypeScope (etRow.Implementation));
			}

			for (int i = 0; i < etTable.Rows.Count; i++) {
				ExportedTypeRow etRow = etTable [i];
				if (etRow.Implementation.TokenType != TokenType.ExportedType)
					continue;

				TypeReference exported = buffer [i];
				TypeReference owner = buffer [etRow.Implementation.RID - 1];
				exported.DeclaringType = owner;
				exported.AttachToScope (owner.Scope);
			}

			for (int i = 0; i < buffer.Length; i++) {
				TypeReference curs = buffer [i];
				if (curs != null)
					ext.Add (curs);
			}
		}

		IMetadataScope GetExportedTypeScope (MetadataToken scope)
		{
			int index = (int) scope.RID - 1;
			switch (scope.TokenType) {
			case TokenType.AssemblyRef:
				return Module.AssemblyReferences [index];
			case TokenType.File:
				return Module.ModuleReferences [index];
			case TokenType.ExportedType:
				return null; // resolved later
			default:
				throw new NotSupportedException ();
			}
		}

		static object GetFixedArgValue (CustomAttrib.FixedArg fa)
		{
			if (fa.SzArray) {
				object [] vals = new object [fa.NumElem];
				for (int j = 0; j < vals.Length; j++)
					vals [j] = fa.Elems [j].Value;
				return vals;
			} else
				return fa.Elems [0].Value;
		}

		TypeReference GetFixedArgType (CustomAttrib.FixedArg fa)
		{
			if (fa.SzArray) {
				if (fa.NumElem == 0)
					return new ArrayType (SearchCoreType (Constants.Object));
				else
					return new ArrayType (fa.Elems [0].ElemType);
			} else
				return fa.Elems [0].ElemType;
		}

		TypeReference GetNamedArgType (CustomAttrib.NamedArg na)
		{
			if (na.FieldOrPropType == ElementType.Boxed)
				return SearchCoreType (Constants.Object);

			return GetFixedArgType (na.FixedArg);
		}

		protected CustomAttribute BuildCustomAttribute (MethodReference ctor, byte [] data, CustomAttrib sig)
		{
			CustomAttribute cattr = new CustomAttribute (ctor);
			if (!sig.Read) {
				cattr.Resolved = false;
				cattr.Blob = data;
				return cattr;
			}

			foreach (CustomAttrib.FixedArg fa in sig.FixedArgs)
				cattr.ConstructorParameters.Add (GetFixedArgValue (fa));

			foreach (CustomAttrib.NamedArg na in sig.NamedArgs) {
				object value = GetFixedArgValue (na.FixedArg);
				if (na.Field) {
					cattr.Fields [na.FieldOrPropName] = value;
					cattr.SetFieldType (na.FieldOrPropName, GetNamedArgType (na));
				} else if (na.Property) {
					cattr.Properties [na.FieldOrPropName] = value;
					cattr.SetPropertyType (na.FieldOrPropName, GetNamedArgType (na));
				} else
					throw new ReflectionException ("Non valid named arg");
			}

			return cattr;
		}

		void CompleteParameter (ParameterDefinition parameter, Param signature, GenericContext context)
		{
			TypeReference paramType;

			if (signature.TypedByRef)
				paramType = SearchCoreType (Constants.TypedReference);
			else
				paramType = GetTypeRefFromSig (signature.Type, context);

			paramType = GetModifierType (signature.CustomMods, paramType);

			if (signature.ByRef)
				paramType = new ReferenceType (paramType);

			parameter.ParameterType = paramType;
		}

		public ParameterDefinition BuildParameterDefinition (int sequence, Param psig, GenericContext context)
		{
			ParameterDefinition parameter = new ParameterDefinition (null);
			parameter.Sequence = sequence;

			CompleteParameter (parameter, psig, context);

			return parameter;
		}

		public ParameterDefinition BuildParameterDefinition (string name, int sequence, ParameterAttributes attrs, Param psig, GenericContext context)
		{
			ParameterDefinition parameter = new ParameterDefinition (name, sequence, attrs, null);

			CompleteParameter (parameter, psig, context);

			return parameter;
		}

		protected SecurityDeclaration BuildSecurityDeclaration (DeclSecurityRow dsRow)
		{
			return BuildSecurityDeclaration (dsRow.Action, m_root.Streams.BlobHeap.Read (dsRow.PermissionSet));
		}

		public SecurityDeclaration BuildSecurityDeclaration (SecurityAction action, byte [] permset)
		{
			if (m_secReader == null)
				m_secReader = new SecurityDeclarationReader (m_root, this);

			return m_secReader.FromByteArray (action, permset);
		}

		protected MarshalSpec BuildMarshalDesc (MarshalSig ms, IHasMarshalSpec container)
		{
			if (ms.Spec is MarshalSig.Array) {
				ArrayMarshalSpec amd = new ArrayMarshalSpec (container);
				MarshalSig.Array ar = (MarshalSig.Array) ms.Spec;
				amd.ElemType = ar.ArrayElemType;
				amd.NumElem = ar.NumElem;
				amd.ParamNum = ar.ParamNum;
				amd.ElemMult = ar.ElemMult;
				return amd;
			} else if (ms.Spec is MarshalSig.CustomMarshaler) {
				CustomMarshalerSpec cmd = new CustomMarshalerSpec (container);
				MarshalSig.CustomMarshaler cmsig = (MarshalSig.CustomMarshaler) ms.Spec;
				cmd.Guid = cmsig.Guid.Length > 0 ? new Guid (cmsig.Guid) : new Guid ();
				cmd.UnmanagedType = cmsig.UnmanagedType;
				cmd.ManagedType = cmsig.ManagedType;
				cmd.Cookie = cmsig.Cookie;
				return cmd;
			} else if (ms.Spec is MarshalSig.FixedArray) {
				FixedArraySpec fad = new FixedArraySpec (container);
				MarshalSig.FixedArray fasig = (MarshalSig.FixedArray) ms.Spec;
				fad.ElemType = fasig.ArrayElemType;
				fad.NumElem = fasig.NumElem;
				return fad;
			} else if (ms.Spec is MarshalSig.FixedSysString) {
				FixedSysStringSpec fssc = new FixedSysStringSpec (container);
				fssc.Size = ((MarshalSig.FixedSysString) ms.Spec).Size;
				return fssc;
			} else if (ms.Spec is MarshalSig.SafeArray) {
				SafeArraySpec sad = new SafeArraySpec (container);
				sad.ElemType = ((MarshalSig.SafeArray) ms.Spec).ArrayElemType;
				return sad;
			} else {
				return new MarshalSpec (ms.NativeInstrinsic, container);
			}
		}

		public TypeReference GetModifierType (CustomMod [] cmods, TypeReference type)
		{
			if (cmods == null || cmods.Length == 0)
				return type;

			TypeReference ret = type;
			for (int i = cmods.Length - 1; i >= 0; i--) {
				CustomMod cmod = cmods [i];
				TypeReference modType;

				if (cmod.TypeDefOrRef.RID == 0)
					continue;

				if (cmod.TypeDefOrRef.TokenType == TokenType.TypeDef)
					modType = GetTypeDefAt (cmod.TypeDefOrRef.RID);
				else
					modType = GetTypeRefAt (cmod.TypeDefOrRef.RID);

				if (cmod.CMOD == CustomMod.CMODType.OPT)
					ret = new ModifierOptional (ret, modType);
				else if (cmod.CMOD == CustomMod.CMODType.REQD)
					ret = new ModifierRequired (ret, modType);
			}
			return ret;
		}

		public MethodReturnType GetMethodReturnType (MethodSig msig, GenericContext context)
		{
			TypeReference retType;
			if (msig.RetType.Void)
				retType = SearchCoreType (Constants.Void);
			else if (msig.RetType.TypedByRef)
				retType = SearchCoreType (Constants.TypedReference);
			else
				retType = GetTypeRefFromSig (msig.RetType.Type, context);

			retType = GetModifierType (msig.RetType.CustomMods, retType);

			if (msig.RetType.ByRef)
				retType = new ReferenceType (retType);

			return new MethodReturnType (retType);
		}

		public TypeReference GetTypeRefFromSig (SigType t, GenericContext context)
		{
			switch (t.ElementType) {
			case ElementType.Class :
				CLASS c = t as CLASS;
				return GetTypeDefOrRef (c.Type, context);
			case ElementType.ValueType :
				VALUETYPE vt = t as VALUETYPE;
				TypeReference vtr = GetTypeDefOrRef (vt.Type, context);
				vtr.IsValueType = true;
				return vtr;
			case ElementType.String :
				return SearchCoreType (Constants.String);
			case ElementType.Object :
				return SearchCoreType (Constants.Object);
			case ElementType.Void :
				return SearchCoreType (Constants.Void);
			case ElementType.Boolean :
				return SearchCoreType (Constants.Boolean);
			case ElementType.Char :
				return SearchCoreType (Constants.Char);
			case ElementType.I1 :
				return SearchCoreType (Constants.SByte);
			case ElementType.U1 :
				return SearchCoreType (Constants.Byte);
			case ElementType.I2 :
				return SearchCoreType (Constants.Int16);
			case ElementType.U2 :
				return SearchCoreType (Constants.UInt16);
			case ElementType.I4 :
				return SearchCoreType (Constants.Int32);
			case ElementType.U4 :
				return SearchCoreType (Constants.UInt32);
			case ElementType.I8 :
				return SearchCoreType (Constants.Int64);
			case ElementType.U8 :
				return SearchCoreType (Constants.UInt64);
			case ElementType.R4 :
				return SearchCoreType (Constants.Single);
			case ElementType.R8 :
				return SearchCoreType (Constants.Double);
			case ElementType.I :
				return SearchCoreType (Constants.IntPtr);
			case ElementType.U :
				return SearchCoreType (Constants.UIntPtr);
			case ElementType.TypedByRef :
				return SearchCoreType (Constants.TypedReference);
			case ElementType.Array :
				ARRAY ary = t as ARRAY;
				return new ArrayType (GetTypeRefFromSig (ary.Type, context), ary.Shape);
			case ElementType.SzArray :
				SZARRAY szary = t as SZARRAY;
				ArrayType at = new ArrayType (GetTypeRefFromSig (szary.Type, context));
				return at;
			case ElementType.Ptr :
				PTR pointer = t as PTR;
				if (pointer.Void)
					return new PointerType (SearchCoreType (Constants.Void));
				return new PointerType (GetTypeRefFromSig (pointer.PtrType, context));
			case ElementType.FnPtr :
				FNPTR funcptr = t as FNPTR;
				FunctionPointerType fnptr = new FunctionPointerType (funcptr.Method.HasThis, funcptr.Method.ExplicitThis,
					funcptr.Method.MethCallConv, GetMethodReturnType (funcptr.Method, context));

				for (int i = 0; i < funcptr.Method.ParamCount; i++) {
					Param p = funcptr.Method.Parameters [i];
					fnptr.Parameters.Add (BuildParameterDefinition (i, p, context));
				}

				CreateSentinelIfNeeded (fnptr, funcptr.Method);

				return fnptr;
			case ElementType.Var:
				VAR var = t as VAR;
				context.CheckProvider (context.Type, var.Index + 1);

				if (context.Type is GenericInstanceType)
					return (context.Type as GenericInstanceType).GenericArguments [var.Index];
				else
					return context.Type.GenericParameters [var.Index];
			case ElementType.MVar:
				MVAR mvar = t as MVAR;
				context.CheckProvider (context.Method, mvar.Index + 1);

				if (context.Method is GenericInstanceMethod)
					return (context.Method as GenericInstanceMethod).GenericArguments [mvar.Index];
				else
					return context.Method.GenericParameters [mvar.Index];
			case ElementType.GenericInst:
				GENERICINST ginst = t as GENERICINST;
				GenericInstanceType instance = new GenericInstanceType (GetTypeDefOrRef (ginst.Type, context));
				instance.IsValueType = ginst.ValueType;
				context.CheckProvider (instance.GetOriginalType (), ginst.Signature.Arity);

				for (int i = 0; i < ginst.Signature.Arity; i++)
					instance.GenericArguments.Add (GetGenericArg (
						ginst.Signature.Types [i], context));

				return instance;
			default:
				break;
			}
			return null;
		}

		TypeReference GetGenericArg (GenericArg arg, GenericContext context)
		{
			TypeReference type = GetTypeRefFromSig (arg.Type, context);
			type = GetModifierType (arg.CustomMods, type);
			return type;
		}

		static bool IsOdd (int i)
		{
			return (i & 1) == 1;
		}

		protected object GetConstant (uint pos, ElementType elemType)
		{
			if (elemType == ElementType.Class)
				return null;

			byte [] constant = m_root.Streams.BlobHeap.Read (pos);

			if (elemType == ElementType.String) {
				int length = constant.Length;
				if (IsOdd (length))
					length--;

				return Encoding.Unicode.GetString (constant, 0, length);
			}

			// One byte types can always be read using BitConverter. However it can't be used
			// elsewhere since it behaves differently in Mono compared to CF on BE architectures
			switch (elemType) {
			case ElementType.Boolean :
				return BitConverter.ToBoolean (constant, 0);
			case ElementType.I1 :
				return (sbyte) constant [0];
			case ElementType.U1 :
				return (byte) constant [0];
			case ElementType.Object: // illegal, but foundable
				return null;
			default :
				if (BitConverter.IsLittleEndian)
					return GetConstantLittleEndian (elemType, constant);
				else
					return GetConstantBigEndian (elemType, constant);
			}
		}

		static object GetConstantLittleEndian (ElementType elemType, byte [] constant)
		{
			switch (elemType) {
			case ElementType.Char :
				return BitConverter.ToChar (constant, 0);
			case ElementType.I2 :
				return BitConverter.ToInt16 (constant, 0);
			case ElementType.I4 :
				return BitConverter.ToInt32 (constant, 0);
			case ElementType.I8 :
				return BitConverter.ToInt64 (constant, 0);
			case ElementType.U2 :
				return BitConverter.ToUInt16 (constant, 0);
			case ElementType.U4 :
				return BitConverter.ToUInt32 (constant, 0);
			case ElementType.U8 :
				return BitConverter.ToUInt64 (constant, 0);
			case ElementType.R4 :
				return BitConverter.ToSingle (constant, 0);
			case ElementType.R8 :
				return BitConverter.ToDouble (constant, 0);
			default:
				throw new ReflectionException ("Non valid element in constant table");
			}
		}

		static object GetConstantBigEndian (ElementType elemType, byte [] constant)
		{
			// BinaryReader always read it's data in LE format
			// note: this could be further optimized (even without unsafe code)
			BinaryReader br = new BinaryReader (new MemoryStream (constant));
			switch (elemType) {
			case ElementType.Char :
				return (char) br.ReadUInt16 ();
			case ElementType.I2 :
				return br.ReadInt16 ();
			case ElementType.I4 :
				return br.ReadInt32 ();
			case ElementType.I8 :
				return br.ReadInt64 ();
			case ElementType.U2 :
				return br.ReadUInt16 ();
			case ElementType.U4 :
				return br.ReadUInt32 ();
			case ElementType.U8 :
				return br.ReadUInt64 ();
			case ElementType.R4 :
				return br.ReadSingle ();
			case ElementType.R8 :
				return br.ReadDouble ();
			default:
				throw new ReflectionException ("Non valid element in constant table");
			}
		}

		protected void SetInitialValue (FieldDefinition field)
		{
			int size = 0;
			TypeReference fieldType = field.FieldType;
			switch (fieldType.FullName) {
			case Constants.Boolean:
			case Constants.Byte:
			case Constants.SByte:
				size = 1;
				break;
			case Constants.Int16:
			case Constants.UInt16:
			case Constants.Char:
				size = 2;
				break;
			case Constants.Int32:
			case Constants.UInt32:
			case Constants.Single:
				size = 4;
				break;
			case Constants.Int64:
			case Constants.UInt64:
			case Constants.Double:
				size = 8;
				break;
			default:
				fieldType = fieldType.GetOriginalType ();

				TypeDefinition fieldTypeDef = fieldType as TypeDefinition;

				if (fieldTypeDef != null)
					size = (int) fieldTypeDef.ClassSize;
				break;
			}

			if (size > 0 && field.RVA != RVA.Zero) {
				byte [] data = new byte [size];
				Section sect = m_reader.Image.GetSectionAtVirtualAddress (field.RVA);
				if (sect != null)
					Buffer.BlockCopy (sect.Data, (int) (long) (field.RVA - sect.VirtualAddress), data, 0, size);
				field.InitialValue = data;
			} else
				field.InitialValue = new byte [0];
		}
	}
}
