#region License
// Copyright (c) 2004 Choy Rim. All rights reserved.
#endregion
#region Imports
using System;
using System.Collections;
using System.Reflection;
using System.Reflection.Emit;
#endregion

namespace DotNetMock.TestFramework
{
	/// <summary>
	/// Creates stub classes that implement an interface.
	/// </summary>
	/// <author>Choy Rim</author>
	public class StubClassMaker
	{
		AssemblyBuilder _assemblyBuilder = null;
		ModuleBuilder _moduleBuilder = null;

		/// <summary>
		/// Create stub class maker.
		/// </summary>
		public StubClassMaker()
		{
			_assemblyBuilder = newAssemblyBuilder(AssemblyBuilderAccess.Run);
			_moduleBuilder = _assemblyBuilder.DefineDynamicModule("ProviderStub");
		}
		/// <summary>
		/// 
		/// </summary>
		/// <param name="supportedInterface">interface which the stub
		/// class will support</param>
		/// <param name="stubMaker">object which will generate
		/// method bodies</param>
		/// <returns>new stub class</returns>
		public Type MakeStubClass(Type supportedInterface, IStubMaker stubMaker) 
		{
			if ( ! supportedInterface.IsInterface ) 
			{
				throw new ArgumentException(
					"Can only stub interfaces.",
					"supportedInterface"
					);
			}
			TypeBuilder typeBuilder = _moduleBuilder.DefineType(
				"ProviderStub",
				TypeAttributes.Public,
				null,
				new Type[] { supportedInterface }
				);
			defineAndImplementStubMethods(typeBuilder, supportedInterface, stubMaker);
			Type stubClass = typeBuilder.CreateType();
			return stubClass;
		}
		/// <summary>
		/// Define stub methods through <see cref="IStubMaker"/>
		/// </summary>
		/// <param name="typeBuilder">where to add methods</param>
		/// <param name="supportedInterface">interface which the stub
		/// class will support</param>
		/// <param name="stubMaker">object which will generate
		/// method bodies</param>
		private static void defineAndImplementStubMethods(
			TypeBuilder typeBuilder,
			Type supportedInterface, IStubMaker stubMaker
			) 
		{
			ArrayList methods = new ArrayList();
			getMethodsForInterface(supportedInterface, methods);
			foreach (MethodInfo mi in methods) 
			{
				MethodBuilder methodBuilder = typeBuilder.DefineMethod(
					mi.Name,
					MethodAttributes.Public | MethodAttributes.Virtual,
					mi.ReturnType,
					getParameterTypes(mi)
					);
				ILGenerator ilg = methodBuilder.GetILGenerator();
				stubMaker.ImplementStubMethod(ilg, mi);
				ilg.Emit(OpCodes.Ret);
			}
		}
		/// <summary>
		/// Returns the array of parameters types given a
		/// <see cref="MethodInfo"/>
		/// </summary>
		/// <param name="mi">method we want the parameter types of</param>
		/// <returns>array of parameter types in method signature</returns>
		private static Type[] getParameterTypes(MethodInfo mi) 
		{
			ArrayList types = new ArrayList();
			foreach (ParameterInfo pi in mi.GetParameters()) 
			{
				types.Add(pi.ParameterType);
			}
			return (Type[]) types.ToArray(typeof(Type));
		}
		private static void getMethodsForInterface(Type type, ArrayList list)
		{
			list.AddRange(type.GetMethods());
			foreach (Type interfaceType in type.GetInterfaces())
			{
				getMethodsForInterface(interfaceType, list);
			}
		}
		private static AssemblyBuilder newAssemblyBuilder(AssemblyBuilderAccess access) 
		{
			AppDomain appDomain = AppDomain.CurrentDomain;
			AssemblyName assemblyName = new AssemblyName();
			assemblyName.Name = "ProviderStubAssembly";
			return appDomain.DefineDynamicAssembly( assemblyName, access );
		}
	}
}
