context: //
  // Import.cs
  //
  // Author:
  //   Jb Evain (jbevain@gmail.com)
  //
  // Copyright (c) 2008 - 2011 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.
  //
  
  using System;
  using System.Collections.Generic;
  using Mono.Collections.Generic;
  using SR = System.Reflection;
  
  using Mono.Cecil.Metadata;
  
  namespace Mono.Cecil {
  
  	enum ImportGenericKind {
  		Definition,
  		Open,
  	}
  
  	struct ImportGenericContext {
  
  		Collection<IGenericParameterProvider> stack;
  
  		public bool IsEmpty { get { return stack == null; } }
  
  		public ImportGenericContext (IGenericParameterProvider provider)
  		{
  			stack = null;
  
  			Push (provider);
  		}
  
  		public void Push (IGenericParameterProvider provider)
  		{
  			if (stack == null)
  				stack = new Collection<IGenericParameterProvider> (1) { provider };
  			else
  				stack.Add (provider);
  		}
  
  		public void Pop ()
  		{
  			stack.RemoveAt (stack.Count - 1);
  		}
  
  		public TypeReference MethodParameter (string method, int position)
  		{
  			for (int i = stack.Count - 1; i >= 0; i--) {
  				var candidate = stack [i] as MethodReference;
  				if (candidate == null)
  					continue;
  
  				if (method != candidate.Name)
  					continue;
  
  				return candidate.GenericParameters [position];
  			}
  
  			throw new InvalidOperationException ();
  		}
  
  		public TypeReference TypeParameter (string type, int position)
  		{
  			for (int i = stack.Count - 1; i >= 0; i--) {
  				var candidate = GenericTypeFor (stack [i]);
  
  				if (candidate.FullName != type)
  					continue;
  
  				return candidate.GenericParameters [position];
  			}
  
  			throw new InvalidOperationException ();
  		}
  
  		static TypeReference GenericTypeFor (IGenericParameterProvider context)
  		{
  			var type = context as TypeReference;
  			if (type != null)
  				return type.GetElementType ();
  
  			var method = context as MethodReference;
  			if (method != null)
  				return method.DeclaringType.GetElementType ();
  
  			throw new InvalidOperationException ();
  		}
  	}
  
  	class MetadataImporter {
  
  		readonly ModuleDefinition module;
  
  		public MetadataImporter (ModuleDefinition module)
  		{
  			this.module = module;
  		}
  
  #if !CF
  		static readonly Dictionary<Type, ElementType> type_etype_mapping = new Dictionary<Type, ElementType> (18) {
  			{ typeof (void), ElementType.Void },
  			{ typeof (bool), ElementType.Boolean },
  			{ typeof (char), ElementType.Char },
  			{ typeof (sbyte), ElementType.I1 },
  			{ typeof (byte), ElementType.U1 },
  			{ typeof (short), ElementType.I2 },
  			{ typeof (ushort), ElementType.U2 },
  			{ typeof (int), ElementType.I4 },
  			{ typeof (uint), ElementType.U4 },
  			{ typeof (long), ElementType.I8 },
  			{ typeof (ulong), ElementType.U8 },
  			{ typeof (float), ElementType.R4 },
  			{ typeof (double), ElementType.R8 },
  			{ typeof (string), ElementType.String },
  			{ typeof (TypedReference), ElementType.TypedByRef },
  			{ typeof (IntPtr), ElementType.I },
  			{ typeof (UIntPtr), ElementType.U },
  			{ typeof (object), ElementType.Object },
  		};
  
  		public TypeReference ImportType (Type type, ImportGenericContext context)
  		{
  			return ImportType (type, context, ImportGenericKind.Open);
  		}
  
  		public TypeReference ImportType (Type type, ImportGenericContext context, ImportGenericKind import_kind)
  		{
  			if (IsTypeSpecification (type) || ImportOpenGenericType (type, import_kind))
  				return ImportTypeSpecification (type, context);
  
  			var reference = new TypeReference (
  				string.Empty,
  				type.Name,
  				module,
  				ImportScope (type.Assembly),
  				type.IsValueType);
  
  			reference.etype = ImportElementType (type);
  
  			if (IsNestedType (type))
  				reference.DeclaringType = ImportType (type.DeclaringType, context, import_kind);
  			else
  				reference.Namespace = type.Namespace ?? string.Empty;
  
  			if (type.IsGenericType)
  				ImportGenericParameters (reference, type.GetGenericArguments ());
  
  			return reference;
  		}
  
  		static bool ImportOpenGenericType (Type type, ImportGenericKind import_kind)
  		{
  			return type.IsGenericType && type.IsGenericTypeDefinition && import_kind == ImportGenericKind.Open;
  		}
  
  		static bool ImportOpenGenericMethod (SR.MethodBase method, ImportGenericKind import_kind)
  		{
  			return method.IsGenericMethod && method.IsGenericMethodDefinition && import_kind == ImportGenericKind.Open;
  		}
  
  		static bool IsNestedType (Type type)
  		{
  #if !SILVERLIGHT
  			return type.IsNested;
  #else
  			return type.DeclaringType != null;
  #endif
  		}
  
  		TypeReference ImportTypeSpecification (Type type, ImportGenericContext context)
  		{
  			if (type.IsByRef)
  				return new ByReferenceType (ImportType (type.GetElementType (), context));
  
  			if (type.IsPointer)
  				return new PointerType (ImportType (type.GetElementType (), context));
  
  			if (type.IsArray)
  				return new ArrayType (ImportType (type.GetElementType (), context), type.GetArrayRank ());
  
  			if (type.IsGenericType)
  				return ImportGenericInstance (type, context);
  
  			if (type.IsGenericParameter)
  				return ImportGenericParameter (type, context);
  
  			throw new NotSupportedException (type.FullName);
  		}
  
  		static TypeReference ImportGenericParameter (Type type, ImportGenericContext context)
  		{
  			if (context.IsEmpty)
  				throw new InvalidOperationException ();
  
  			if (type.DeclaringMethod != null)
  				return context.MethodParameter (type.DeclaringMethod.Name, type.GenericParameterPosition);
  
  			if (type.DeclaringType != null)
  				return  context.TypeParameter (NormalizedFullName (type.DeclaringType), type.GenericParameterPosition);
  
  			throw new InvalidOperationException();
  		}
  
  		private static string NormalizedFullName (Type type)
  		{
  			if (IsNestedType (type))
  				return NormalizedFullName (type.DeclaringType) + "/" + type.Name;
  
  			return type.FullName;
  		}
  
  		TypeReference ImportGenericInstance (Type type, ImportGenericContext context)
  		{
  			var element_type = ImportType (type.GetGenericTypeDefinition (), context, ImportGenericKind.Definition);
  			var instance = new GenericInstanceType (element_type);
  			var arguments = type.GetGenericArguments ();
  			var instance_arguments = instance.GenericArguments;
  
  			context.Push (element_type);
  			try {
  				for (int i = 0; i < arguments.Length; i++)
  					instance_arguments.Add (ImportType (arguments [i], context));
  
  				return instance;
  			} finally {
  				context.Pop ();
  			}
  		}
  
  		static bool IsTypeSpecification (Type type)
  		{
  			return type.HasElementType
  				|| IsGenericInstance (type)
  				|| type.IsGenericParameter;
  		}
  
  		static bool IsGenericInstance (Type type)
  		{
  			return type.IsGenericType && !type.IsGenericTypeDefinition;
  		}
  
  		static ElementType ImportElementType (Type type)
  		{
  			ElementType etype;
  			if (!type_etype_mapping.TryGetValue (type, out etype))
  				return ElementType.None;
  
  			return etype;
  		}
  
  		AssemblyNameReference ImportScope (SR.Assembly assembly)
  		{
  			AssemblyNameReference scope;
  #if !SILVERLIGHT
  			var name = assembly.GetName ();
  
  			if (TryGetAssemblyNameReference (name, out scope))
  				return scope;
  
  			scope = new AssemblyNameReference (name.Name, name.Version) {
  				Culture = name.CultureInfo.Name,
  				PublicKeyToken = name.GetPublicKeyToken (),
  				HashAlgorithm = (AssemblyHashAlgorithm) name.HashAlgorithm,
  			};
  
  			module.AssemblyReferences.Add (scope);
  
  			return scope;
  #else
  			var name = AssemblyNameReference.Parse (assembly.FullName);
  
  			if (TryGetAssemblyNameReference (name, out scope))
  				return scope;
  
  			module.AssemblyReferences.Add (name);
  
  			return name;
  #endif
  		}
  
  #if !SILVERLIGHT
  		bool TryGetAssemblyNameReference (SR.AssemblyName name, out AssemblyNameReference assembly_reference)
  		{
  			var references = module.AssemblyReferences;
  
  			for (int i = 0; i < references.Count; i++) {
  				var reference = references [i];
  				if (name.FullName != reference.FullName) // TODO compare field by field
  					continue;
  
  				assembly_reference = reference;
  				return true;
  			}
  
  			assembly_reference = null;
  			return false;
  		}
  #endif
  
  		public FieldReference ImportField (SR.FieldInfo field, ImportGenericContext context)
  		{
  			var declaring_type = ImportType (field.DeclaringType, context);
  
  			if (IsGenericInstance (field.DeclaringType))
  				field = ResolveFieldDefinition (field);
  
  			context.Push (declaring_type);
  			try {
  				return new FieldReference {
  					Name = field.Name,
  					DeclaringType = declaring_type,
  					FieldType = ImportType (field.FieldType, context),
  				};
  			} finally {
  				context.Pop ();
  			}
  		}
  
  		static SR.FieldInfo ResolveFieldDefinition (SR.FieldInfo field)
  		{
  #if !SILVERLIGHT
  			return field.Module.ResolveField (field.MetadataToken);
  #else
  			return field.DeclaringType.GetGenericTypeDefinition ().GetField (field.Name,
  				SR.BindingFlags.Public
  				| SR.BindingFlags.NonPublic
  				| (field.IsStatic ? SR.BindingFlags.Static : SR.BindingFlags.Instance));
  #endif
  		}
  
  		public MethodReference ImportMethod (SR.MethodBase method, ImportGenericContext context, ImportGenericKind import_kind)
  		{
  			if (IsMethodSpecification (method) || ImportOpenGenericMethod (method, import_kind))
  				return ImportMethodSpecification (method, context);
  
  			var declaring_type = ImportType (method.DeclaringType, context);
  
  			if (IsGenericInstance (method.DeclaringType))
  				method = method.Module.ResolveMethod (method.MetadataToken);
  
  			var reference = new MethodReference {
  				Name = method.Name,
  				HasThis = HasCallingConvention (method, SR.CallingConventions.HasThis),
  				ExplicitThis = HasCallingConvention (method, SR.CallingConventions.ExplicitThis),
  				DeclaringType = ImportType (method.DeclaringType, context, ImportGenericKind.Definition),
  			};
  
  			if (HasCallingConvention (method, SR.CallingConventions.VarArgs))
  				reference.CallingConvention &= MethodCallingConvention.VarArg;
  
  			if (method.IsGenericMethod)
  				ImportGenericParameters (reference, method.GetGenericArguments ());
  
  			context.Push (reference);
  			try {
  				var method_info = method as SR.MethodInfo;
  				reference.ReturnType = method_info != null
  					? ImportType (method_info.ReturnType, context)
  					: ImportType (typeof (void), default (ImportGenericContext));
  
  				var parameters = method.GetParameters ();
  				var reference_parameters = reference.Parameters;
  
  				for (int i = 0; i < parameters.Length; i++)
  					reference_parameters.Add (
  						new ParameterDefinition (ImportType (parameters [i].ParameterType, context)));
  
  				reference.DeclaringType = declaring_type;
  
  				return reference;
  			} finally {
  				context.Pop ();
  			}
  		}
  
  		static void ImportGenericParameters (IGenericParameterProvider provider, Type [] arguments)
  		{
  			var provider_parameters = provider.GenericParameters;
  
  			for (int i = 0; i < arguments.Length; i++)
  				provider_parameters.Add (new GenericParameter (arguments [i].Name, provider));
  		}
  
  		static bool IsMethodSpecification (SR.MethodBase method)
  		{
  			return method.IsGenericMethod && !method.IsGenericMethodDefinition;
  		}
  
  		MethodReference ImportMethodSpecification (SR.MethodBase method, ImportGenericContext context)
  		{
  			var method_info = method as SR.MethodInfo;
  			if (method_info == null)
  				throw new InvalidOperationException ();
  
  			var element_method = ImportMethod (method_info.GetGenericMethodDefinition (), context, ImportGenericKind.Definition);
  			var instance = new GenericInstanceMethod (element_method);
  			var arguments = method.GetGenericArguments ();
  			var instance_arguments = instance.GenericArguments;
  
  			context.Push (element_method);
  			try {
  				for (int i = 0; i < arguments.Length; i++)
  					instance_arguments.Add (ImportType (arguments [i], context));
  
  				return instance;
  			} finally {
  				context.Pop ();
  			}
  		}
  
  		static bool HasCallingConvention (SR.MethodBase method, SR.CallingConventions conventions)
  		{
  			return (method.CallingConvention & conventions) != 0;
  		}
  #endif
  
  		public TypeReference ImportType (TypeReference type, ImportGenericContext context)
  		{
  			if (type.IsTypeSpecification ())
  				return ImportTypeSpecification (type, context);
  
  			var reference = new TypeReference (
  				type.Namespace,
  				type.Name,
  				module,
  				ImportScope (type.Scope),
  				type.IsValueType);
  
  			MetadataSystem.TryProcessPrimitiveTypeReference (reference);
  
  			if (type.IsNested)
  				reference.DeclaringType = ImportType (type.DeclaringType, context);
  
  			if (type.HasGenericParameters)
  				ImportGenericParameters (reference, type);
  
  			return reference;
  		}
  
  		IMetadataScope ImportScope (IMetadataScope scope)
  		{
  
gt: 			switch (scope.MetadataScopeType) {