/*
  Copyright (C) 2011-2013 Jeroen Frijters

  This software is provided 'as-is', without any express or implied
  warranty.  In no event will the authors be held liable for any damages
  arising from the use of this software.

  Permission is granted to anyone to use this software for any purpose,
  including commercial applications, and to alter it and redistribute it
  freely, subject to the following restrictions:

  1. The origin of this software must not be misrepresented; you must not
     claim that you wrote the original software. If you use this software
     in a product, an acknowledgment in the product documentation would be
     appreciated but is not required.
  2. Altered source versions must be plainly marked as such, and must not be
     misrepresented as being the original software.
  3. This notice may not be removed or altered from any source distribution.

  Jeroen Frijters
  jeroen@frijters.net
  
*/

using System;
using System.Reflection;
using System.Reflection.Emit;
using IKVM.Internal;
using java.lang.invoke;
using jlClass = java.lang.Class;

static class Java_java_lang_invoke_BoundMethodHandle
{
	public static object createDelegate(MethodType newType, MethodHandle mh, int argnum, object argument)
	{
#if FIRST_PASS
		return null;
#else
		Delegate del = (Delegate)mh.vmtarget;
		if (argnum == 0
			&& del.Target == null
			// we don't have to check for instance methods on a Value Type, because DirectMethodHandle can't use a direct delegate for that anyway
			&& (!del.Method.IsStatic || !del.Method.GetParameters()[0].ParameterType.IsValueType)
			&& !ReflectUtil.IsDynamicMethod(del.Method))
		{
			return Delegate.CreateDelegate(MethodHandleUtil.CreateDelegateType(newType), argument, del.Method);
		}
		else
		{
			// slow path where we're generating a DynamicMethod
			if (mh.type().parameterType(argnum).isPrimitive())
			{
				argument = JVM.Unbox(argument);
			}
			MethodHandleUtil.DynamicMethodBuilder dm = new MethodHandleUtil.DynamicMethodBuilder("BoundMethodHandle", newType, mh, argument);
			for (int i = 0, count = mh.type().parameterCount(), pos = 0; i < count; i++)
			{
				if (i == argnum)
				{
					dm.LoadValue();
				}
				else
				{
					dm.Ldarg(pos++);
				}
			}
			dm.CallTarget();
			dm.Ret();
			return dm.CreateDelegate();
		}
#endif
	}
}

static class Java_java_lang_invoke_CallSite
{
	public static object createIndyCallSite(object target)
	{
		return Activator.CreateInstance(typeof(IKVM.Runtime.IndyCallSite<>).MakeGenericType(target.GetType()), true);
	}
}

static class Java_java_lang_invoke_DirectMethodHandle
{
    public static object createDelegate(MethodType type, MemberName m, bool doDispatch, jlClass lookupClass)
	{
#if FIRST_PASS
		return null;
#else
		int index = m.getVMIndex();
		if (index == Int32.MaxValue)
		{
			bool invokeExact = m.getName() == "invokeExact";
			Type targetDelegateType = MethodHandleUtil.CreateDelegateType(invokeExact ? type.dropParameterTypes(0, 1) : type);
			MethodHandleUtil.DynamicMethodBuilder dm = new MethodHandleUtil.DynamicMethodBuilder("DirectMethodHandle." + m.getName(), type, typeof(IKVM.Runtime.InvokeCache<>).MakeGenericType(targetDelegateType));
			dm.Ldarg(0);
			if (invokeExact)
			{
				dm.Call(ByteCodeHelperMethods.GetDelegateForInvokeExact.MakeGenericMethod(targetDelegateType));
			}
			else
			{
				dm.LoadValueAddress();
				dm.Call(ByteCodeHelperMethods.GetDelegateForInvoke.MakeGenericMethod(targetDelegateType));
				dm.Ldarg(0);
			}
			for (int i = 1, count = type.parameterCount(); i < count; i++)
			{
				dm.Ldarg(i);
			}
			dm.CallDelegate(targetDelegateType);
			dm.Ret();
			return dm.CreateDelegate();
		}
		else
		{
			TypeWrapper tw = (TypeWrapper)typeof(MemberName).GetField("vmtarget", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(m);
			tw.Finish();
			MethodWrapper mw = tw.GetMethods()[index];
			if (mw.IsDynamicOnly)
			{
				MethodHandleUtil.DynamicMethodBuilder dm = new MethodHandleUtil.DynamicMethodBuilder("CustomInvoke:" + mw.Name, type, mw);
				if (mw.IsStatic)
				{
					dm.LoadNull();
					dm.BoxArgs(0);
				}
				else
				{
					dm.Ldarg(0);
					dm.BoxArgs(1);
				}
				dm.Callvirt(typeof(MethodWrapper).GetMethod("Invoke", BindingFlags.Instance | BindingFlags.NonPublic));
				dm.UnboxReturnValue();
				dm.Ret();
				return dm.CreateDelegate();
			}
			// HACK this code is duplicated in compiler.cs
			if (mw.IsProtected && (mw.DeclaringType == CoreClasses.java.lang.Object.Wrapper || mw.DeclaringType == CoreClasses.java.lang.Throwable.Wrapper))
			{
				TypeWrapper thisType = TypeWrapper.FromClass(lookupClass);
				TypeWrapper cli_System_Object = ClassLoaderWrapper.LoadClassCritical("cli.System.Object");
				TypeWrapper cli_System_Exception = ClassLoaderWrapper.LoadClassCritical("cli.System.Exception");
				// HACK we may need to redirect finalize or clone from java.lang.Object/Throwable
				// to a more specific base type.
				if (thisType.IsAssignableTo(cli_System_Object))
				{
					mw = cli_System_Object.GetMethodWrapper(mw.Name, mw.Signature, true);
				}
				else if (thisType.IsAssignableTo(cli_System_Exception))
				{
					mw = cli_System_Exception.GetMethodWrapper(mw.Name, mw.Signature, true);
				}
				else if (thisType.IsAssignableTo(CoreClasses.java.lang.Throwable.Wrapper))
				{
					mw = CoreClasses.java.lang.Throwable.Wrapper.GetMethodWrapper(mw.Name, mw.Signature, true);
				}
			}
			mw.ResolveMethod();
			MethodInfo mi = mw.GetMethod() as MethodInfo;
			if (mi != null
				&& !mw.HasCallerID
				&& !tw.IsRemapped
				&& !tw.IsGhost
				&& !tw.IsNonPrimitiveValueType
				&& type.parameterCount() <= MethodHandleUtil.MaxArity
				// FXBUG we should be able to use a normal (unbound) delegate for virtual methods
				// (when doDispatch is set), but the x64 CLR crashes when doing a virtual method dispatch on
				// a null reference
				&& (!mi.IsVirtual || (doDispatch && IntPtr.Size == 4))
				&& (doDispatch || !mi.IsVirtual))
			{
				return Delegate.CreateDelegate(MethodHandleUtil.CreateDelegateType(tw, mw), mi);
			}
			else
			{
				// slow path where we emit a DynamicMethod
				MethodHandleUtil.DynamicMethodBuilder dm = new MethodHandleUtil.DynamicMethodBuilder(mw.DeclaringType.TypeAsBaseType, "DirectMethodHandle:" + mw.Name, type,
					mw.HasCallerID ? ikvm.@internal.CallerID.create(lookupClass) : null);
				for (int i = 0, count = type.parameterCount(); i < count; i++)
				{
					if (i == 0 && !mw.IsStatic && (tw.IsGhost || tw.IsNonPrimitiveValueType))
					{
						dm.LoadFirstArgAddress();
					}
					else
					{
						dm.Ldarg(i);
					}
				}
				if (mw.HasCallerID)
				{
					dm.LoadCallerID();
				}
				if (doDispatch && !mw.IsStatic)
				{
					dm.Callvirt(mw);
				}
				else
				{
					dm.Call(mw);
				}
				dm.Ret();
				return dm.CreateDelegate();
			}
		}
#endif
	}
}

static class Java_java_lang_invoke_MethodHandle
{
	private static IKVM.Runtime.InvokeCache<IKVM.Runtime.MH<MethodHandle, object[], object>> cache;

	public static object invokeExact(MethodHandle mh, object[] args)
	{
#if FIRST_PASS
		return null;
#else
		return IKVM.Runtime.ByteCodeHelper.GetDelegateForInvokeExact<IKVM.Runtime.MH<object[], object>>(mh)(args);
#endif
	}

	public static object invoke(MethodHandle mh, object[] args)
	{
#if FIRST_PASS
		return null;
#else
		MethodType type = mh.type();
		if (mh.isVarargsCollector())
		{
			java.lang.Class varargType = type.parameterType(type.parameterCount() - 1);
			if (type.parameterCount() == args.Length)
			{
				if (!varargType.isInstance(args[args.Length - 1]))
				{
					Array arr = (Array)java.lang.reflect.Array.newInstance(varargType.getComponentType(), 1);
					arr.SetValue(args[args.Length - 1], 0);
					args[args.Length - 1] = arr;
				}
			}
			else if (type.parameterCount() - 1 > args.Length)
			{
				throw new WrongMethodTypeException();
			}
			else
			{
				object[] newArgs = new object[type.parameterCount()];
				Array.Copy(args, newArgs, newArgs.Length - 1);
				Array varargs = (Array)java.lang.reflect.Array.newInstance(varargType.getComponentType(), args.Length - (newArgs.Length - 1));
				Array.Copy(args, newArgs.Length - 1, varargs, 0, varargs.Length);
				newArgs[newArgs.Length - 1] = varargs;
				args = newArgs;
			}
		}
		if (mh.type().parameterCount() != args.Length)
		{
			throw new WrongMethodTypeException();
		}
		mh = mh.asSpreader(typeof(object[]), args.Length);
		return IKVM.Runtime.ByteCodeHelper.GetDelegateForInvoke<IKVM.Runtime.MH<MethodHandle, object[], object>>(mh, ref cache)(mh, args);
#endif
	}
}

static partial class MethodHandleUtil
{
	internal static Type CreateDelegateType(MethodType type)
	{
#if FIRST_PASS
		return null;
#else
		TypeWrapper[] args = new TypeWrapper[type.parameterCount()];
		for (int i = 0; i < args.Length; i++)
		{
			args[i] = TypeWrapper.FromClass(type.parameterType(i));
			args[i].Finish();
		}
		TypeWrapper ret = TypeWrapper.FromClass(type.returnType());
		ret.Finish();
		return CreateDelegateType(args, ret);
#endif
	}

#if !FIRST_PASS
	private static Type[] GetParameterTypes(MethodBase mb)
	{
		ParameterInfo[] pi = mb.GetParameters();
		Type[] args = new Type[pi.Length];
		for (int i = 0; i < args.Length; i++)
		{
			args[i] = pi[i].ParameterType;
		}
		return args;
	}

	private static Type[] GetParameterTypes(Type thisType, MethodBase mb)
	{
		ParameterInfo[] pi = mb.GetParameters();
		Type[] args = new Type[pi.Length + 1];
		args[0] = thisType;
		for (int i = 1; i < args.Length; i++)
		{
			args[i] = pi[i - 1].ParameterType;
		}
		return args;
	}

	internal static MethodType GetDelegateMethodType(Type type)
	{
		java.lang.Class[] types;
		MethodInfo mi = GetDelegateInvokeMethod(type);
		ParameterInfo[] pi = mi.GetParameters();
		if (pi.Length > 0 && IsPackedArgsContainer(pi[pi.Length - 1].ParameterType))
		{
			System.Collections.Generic.List<java.lang.Class> list = new System.Collections.Generic.List<java.lang.Class>();
			for (int i = 0; i < pi.Length - 1; i++)
			{
				list.Add(ClassLoaderWrapper.GetWrapperFromType(pi[i].ParameterType).ClassObject);
			}
			Type[] args = pi[pi.Length - 1].ParameterType.GetGenericArguments();
			while (IsPackedArgsContainer(args[args.Length - 1]))
			{
				for (int i = 0; i < args.Length - 1; i++)
				{
					list.Add(ClassLoaderWrapper.GetWrapperFromType(args[i]).ClassObject);
				}
				args = args[args.Length - 1].GetGenericArguments();
			}
			for (int i = 0; i < args.Length; i++)
			{
				list.Add(ClassLoaderWrapper.GetWrapperFromType(args[i]).ClassObject);
			}
			types = list.ToArray();
		}
		else
		{
			types = new java.lang.Class[pi.Length];
			for (int i = 0; i < types.Length; i++)
			{
				types[i] = ClassLoaderWrapper.GetWrapperFromType(pi[i].ParameterType).ClassObject;
			}
		}
		return MethodType.methodType(ClassLoaderWrapper.GetWrapperFromType(mi.ReturnType).ClassObject, types);
	}

	internal sealed class DynamicMethodBuilder
	{
		private readonly MethodType type;
		private readonly int firstArg;
		private readonly Type delegateType;
		private readonly object firstBoundValue;
		private readonly object secondBoundValue;
		private readonly Type container;
		private readonly DynamicMethod dm;
		private readonly CodeEmitter ilgen;
		private readonly Type packedArgType;
		private readonly int packedArgPos;

		sealed class Container<T1, T2>
		{
			public T1 target;
			public T2 value;

			public Container(T1 target, T2 value)
			{
				this.target = target;
				this.value = value;
			}
		}

		private DynamicMethodBuilder(string name, MethodType type, Type container, object target, object value, Type owner)
		{
			this.type = type;
			this.delegateType = CreateDelegateType(type);
			this.firstBoundValue = target;
			this.secondBoundValue = value;
			this.container = container;
			MethodInfo mi = GetDelegateInvokeMethod(delegateType);
			Type[] paramTypes;
			if (container != null)
			{
				this.firstArg = 1;
				paramTypes = GetParameterTypes(container, mi);
			}
			else if (target != null)
			{
				this.firstArg = 1;
				paramTypes = GetParameterTypes(target.GetType(), mi);
			}
			else
			{
				paramTypes = GetParameterTypes(mi);
			}
			if (!ReflectUtil.CanOwnDynamicMethod(owner))
			{
				owner = typeof(DynamicMethodBuilder);
			}
			this.dm = new DynamicMethod(name, mi.ReturnType, paramTypes, owner, true);
			this.ilgen = CodeEmitter.Create(dm);

			if (type.parameterCount() > MaxArity)
			{
				ParameterInfo[] pi = mi.GetParameters();
				this.packedArgType = pi[pi.Length - 1].ParameterType;
				this.packedArgPos = pi.Length - 1 + firstArg;
			}
			else
			{
				this.packedArgPos = Int32.MaxValue;
			}
		}

		internal DynamicMethodBuilder(Type owner, string name, MethodType type, ikvm.@internal.CallerID callerID)
			: this(name, type, null, callerID, null, owner)
		{
		}

		internal DynamicMethodBuilder(string name, MethodType type, MethodHandle target)
			: this(name, type, null, target.vmtarget, null, null)
		{
			ilgen.Emit(OpCodes.Ldarg_0);
		}

		internal DynamicMethodBuilder(string name, MethodType type, MethodWrapper target)
			: this(name, type, null, target, null, null)
		{
			ilgen.Emit(OpCodes.Ldarg_0);
		}

		internal DynamicMethodBuilder(string name, MethodType type, MethodHandle target, object value)
			: this(name, type, typeof(Container<,>).MakeGenericType(target.vmtarget.GetType(), value.GetType()), target.vmtarget, value, null)
		{
			ilgen.Emit(OpCodes.Ldarg_0);
			ilgen.Emit(OpCodes.Ldfld, container.GetField("target"));
		}

		internal DynamicMethodBuilder(string name, MethodType type, Type valueType)
			: this(name, type, typeof(Container<,>).MakeGenericType(typeof(object), valueType), null, null, null)
		{
		}

		internal void Call(MethodInfo method)
		{
			ilgen.Emit(OpCodes.Call, method);
		}

		internal void Callvirt(MethodInfo method)
		{
			ilgen.Emit(OpCodes.Callvirt, method);
		}

		internal void Call(MethodWrapper mw)
		{
			mw.EmitCall(ilgen);
		}

		internal void Callvirt(MethodWrapper mw)
		{
			mw.EmitCallvirt(ilgen);
		}

		internal void CallDelegate(Type delegateType)
		{
			EmitCallDelegateInvokeMethod(ilgen, delegateType);
		}

		internal void LoadFirstArgAddress()
		{
			ilgen.EmitLdarga(firstArg);
		}

		internal void Ldarg(int i)
		{
			i += firstArg;
			if (i >= packedArgPos)
			{
				ilgen.EmitLdarga(packedArgPos);
				int fieldPos = i - packedArgPos;
				Type type = packedArgType;
				while (fieldPos >= MaxArity || (fieldPos == MaxArity - 1 && IsPackedArgsContainer(type.GetField("t8").FieldType)))
				{
					FieldInfo field = type.GetField("t8");
					type = field.FieldType;
					ilgen.Emit(OpCodes.Ldflda, field);
					fieldPos -= MaxArity - 1;
				}
				ilgen.Emit(OpCodes.Ldfld, type.GetField("t" + (1 + fieldPos)));
			}
			else
			{
				ilgen.EmitLdarg(i);
			}
		}

		internal void LoadArrayElement(int index, TypeWrapper tw)
		{
			ilgen.EmitLdc_I4(index);
			if (tw.IsNonPrimitiveValueType)
			{
				ilgen.Emit(OpCodes.Ldelema, tw.TypeAsArrayType);
				ilgen.Emit(OpCodes.Ldobj, tw.TypeAsArrayType);
			}
			else if (tw == PrimitiveTypeWrapper.BYTE
				|| tw == PrimitiveTypeWrapper.BOOLEAN)
			{
				ilgen.Emit(OpCodes.Ldelem_I1);
			}
			else if (tw == PrimitiveTypeWrapper.SHORT)
			{
				ilgen.Emit(OpCodes.Ldelem_I2);
			}
			else if (tw == PrimitiveTypeWrapper.CHAR)
			{
				ilgen.Emit(OpCodes.Ldelem_U2);
			}
			else if (tw == PrimitiveTypeWrapper.INT)
			{
				ilgen.Emit(OpCodes.Ldelem_I4);
			}
			else if (tw == PrimitiveTypeWrapper.LONG)
			{
				ilgen.Emit(OpCodes.Ldelem_I8);
			}
			else if (tw == PrimitiveTypeWrapper.FLOAT)
			{
				ilgen.Emit(OpCodes.Ldelem_R4);
			}
			else if (tw == PrimitiveTypeWrapper.DOUBLE)
			{
				ilgen.Emit(OpCodes.Ldelem_R8);
			}
			else
			{
				ilgen.Emit(OpCodes.Ldelem_Ref);
				if (tw.IsGhost)
				{
					tw.EmitConvStackTypeToSignatureType(ilgen, null);
				}
			}
		}

		internal void Convert(java.lang.Class srcType, java.lang.Class dstType, int level)
		{
			EmitConvert(ilgen, srcType, dstType, level);
		}

		internal void CallTarget()
		{
			EmitCallDelegateInvokeMethod(ilgen, firstBoundValue.GetType());
		}

		internal void LoadCallerID()
		{
			ilgen.Emit(OpCodes.Ldarg_0);
		}

		internal void LoadValueAddress()
		{
			ilgen.Emit(OpCodes.Ldarg_0);
			ilgen.Emit(OpCodes.Ldflda, container.GetField("value"));
		}

		internal void LoadValue()
		{
			ilgen.Emit(OpCodes.Ldarg_0);
			ilgen.Emit(OpCodes.Ldfld, container.GetField("value"));
		}

		internal void CallValue()
		{
			EmitCallDelegateInvokeMethod(ilgen, secondBoundValue.GetType());
		}

		internal void Ret()
		{
			ilgen.Emit(OpCodes.Ret);
		}

		internal Delegate CreateDelegate()
		{
			ilgen.DoEmit();
			return ValidateDelegate(firstArg == 0
				? dm.CreateDelegate(delegateType)
				: dm.CreateDelegate(delegateType, container == null ? firstBoundValue : Activator.CreateInstance(container, firstBoundValue, secondBoundValue)));
		}

		internal AdapterMethodHandle CreateAdapter()
		{
			return new AdapterMethodHandle(type, CreateDelegate());
		}

		internal void BoxArgs(int start)
		{
			int paramCount = type.parameterCount();
			ilgen.EmitLdc_I4(paramCount - start);
			ilgen.Emit(OpCodes.Newarr, Types.Object);
			for (int i = start; i < paramCount; i++)
			{
				ilgen.Emit(OpCodes.Dup);
				ilgen.EmitLdc_I4(i - start);
				Ldarg(i);
				TypeWrapper tw = TypeWrapper.FromClass(type.parameterType(i));
				if (tw.IsPrimitive || tw.IsGhost)
				{
					ilgen.Emit(OpCodes.Box, tw.TypeAsSignatureType);
				}
				else if (tw.IsNonPrimitiveValueType)
				{
					tw.EmitBox(ilgen);
				}
				ilgen.Emit(OpCodes.Stelem_Ref);
			}
		}

		internal void UnboxReturnValue()
		{
			TypeWrapper tw = TypeWrapper.FromClass(type.returnType());
			if (tw == PrimitiveTypeWrapper.VOID)
			{
				ilgen.Emit(OpCodes.Pop);
			}
			else if (tw.IsPrimitive || tw.IsGhost)
			{
				ilgen.Emit(OpCodes.Unbox, tw.TypeAsSignatureType);
				ilgen.Emit(OpCodes.Ldobj, tw.TypeAsSignatureType);
			}
			else if (tw.IsNonPrimitiveValueType)
			{
				tw.EmitUnbox(ilgen);
			}
			else if (tw != CoreClasses.java.lang.Object.Wrapper)
			{
				tw.EmitCheckcast(ilgen);
			}
		}

		internal void LoadNull()
		{
			ilgen.Emit(OpCodes.Ldnull);
		}
	}

#if DEBUG
	[System.Security.SecuritySafeCritical]
#endif
	private static Delegate ValidateDelegate(Delegate d)
	{
#if DEBUG
		try
		{
			System.Runtime.CompilerServices.RuntimeHelpers.PrepareDelegate(d);
		}
		catch (Exception x)
		{
			JVM.CriticalFailure("Delegate failed to JIT", x);
		}
#endif
		return d;
	}

	private struct BoxUtil
	{
		private static readonly BoxUtil[] boxers = new BoxUtil[] {
			BoxUtil.Create<java.lang.Boolean, bool>(java.lang.Boolean.TYPE, "boolean", "Boolean"),
			BoxUtil.Create<java.lang.Byte, byte>(java.lang.Byte.TYPE, "byte", "Byte"),
			BoxUtil.Create<java.lang.Character, char>(java.lang.Character.TYPE, "char", "Character"),
			BoxUtil.Create<java.lang.Short, short>(java.lang.Short.TYPE, "short", "Short"),
			BoxUtil.Create<java.lang.Integer, int>(java.lang.Integer.TYPE, "int", "Integer"),
			BoxUtil.Create<java.lang.Long, int>(java.lang.Long.TYPE, "long", "Long"),
			BoxUtil.Create<java.lang.Float, float>(java.lang.Float.TYPE, "float", "Float"),
			BoxUtil.Create<java.lang.Double, double>(java.lang.Double.TYPE, "double", "Double"),
		};
		private readonly jlClass clazz;
		private readonly jlClass type;
		private readonly MethodInfo box;
		private readonly MethodInfo unbox;
		private readonly MethodInfo unboxObject;

		private BoxUtil(jlClass clazz, jlClass type, MethodInfo box, MethodInfo unbox, MethodInfo unboxObject)
		{
			this.clazz = clazz;
			this.type = type;
			this.box = box;
			this.unbox = unbox;
			this.unboxObject = unboxObject;
		}

		private static BoxUtil Create<T, P>(jlClass type, string name, string longName)
		{
			return new BoxUtil(ikvm.@internal.ClassLiteral<T>.Value, type,
				typeof(T).GetMethod("valueOf", new Type[] { typeof(P) }),
				typeof(T).GetMethod(name + "Value", Type.EmptyTypes),
				typeof(sun.invoke.util.ValueConversions).GetMethod("unbox" + longName, BindingFlags.Static | BindingFlags.NonPublic));
		}

		internal static void Box(CodeEmitter ilgen, jlClass srcClass, jlClass dstClass, int level)
		{
			for (int i = 0; i < boxers.Length; i++)
			{
				if (boxers[i].type == srcClass)
				{
					ilgen.Emit(OpCodes.Call, boxers[i].box);
					EmitConvert(ilgen, boxers[i].clazz, dstClass, level);
					return;
				}
			}
			throw new InvalidOperationException();
		}

		internal static void Unbox(CodeEmitter ilgen, jlClass srcClass, jlClass dstClass, int level)
		{
			for (int i = 0; i < boxers.Length; i++)
			{
				if (boxers[i].clazz == srcClass)
				{
					// typed unboxing
					ilgen.Emit(OpCodes.Call, boxers[i].unbox);
					EmitConvert(ilgen, boxers[i].type, dstClass, level);
					return;
				}
			}
			for (int i = 0; i < boxers.Length; i++)
			{
				if (boxers[i].type == dstClass)
				{
					// untyped unboxing
					ilgen.EmitLdc_I4(level > 1 ? 1 : 0);
					ilgen.Emit(OpCodes.Call, boxers[i].unboxObject);
					return;
				}
			}
			throw new InvalidOperationException();
		}
	}

	private static void EmitConvert(CodeEmitter ilgen, java.lang.Class srcClass, java.lang.Class dstClass, int level)
	{
		if (srcClass != dstClass)
		{
			TypeWrapper src = TypeWrapper.FromClass(srcClass);
			TypeWrapper dst = TypeWrapper.FromClass(dstClass);
			src.Finish();
			dst.Finish();
			if (src.IsNonPrimitiveValueType)
			{
				src.EmitBox(ilgen);
			}
			if (dst == PrimitiveTypeWrapper.VOID)
			{
				ilgen.Emit(OpCodes.Pop);
			}
			else if (src.IsPrimitive)
			{
				if (dst.IsPrimitive)
				{
					if (src == PrimitiveTypeWrapper.BYTE)
					{
						ilgen.Emit(OpCodes.Conv_I1);
					}
					if (dst == PrimitiveTypeWrapper.FLOAT)
					{
						ilgen.Emit(OpCodes.Conv_R4);
					}
					else if (dst == PrimitiveTypeWrapper.DOUBLE)
					{
						ilgen.Emit(OpCodes.Conv_R8);
					}
					else if (dst == PrimitiveTypeWrapper.LONG)
					{
						if (src == PrimitiveTypeWrapper.FLOAT)
						{
							ilgen.Emit(OpCodes.Call, ByteCodeHelperMethods.f2l);
						}
						else if (src == PrimitiveTypeWrapper.DOUBLE)
						{
							ilgen.Emit(OpCodes.Call, ByteCodeHelperMethods.d2l);
						}
						else
						{
							ilgen.Emit(OpCodes.Conv_I8);
						}
					}
					else if (dst == PrimitiveTypeWrapper.BOOLEAN)
					{
						if (src == PrimitiveTypeWrapper.FLOAT)
						{
							ilgen.Emit(OpCodes.Call, ByteCodeHelperMethods.f2i);
						}
						else if (src == PrimitiveTypeWrapper.DOUBLE)
						{
							ilgen.Emit(OpCodes.Call, ByteCodeHelperMethods.d2i);
						}
						else
						{
							ilgen.Emit(OpCodes.Conv_I4);
						}
						ilgen.Emit(OpCodes.Ldc_I4_1);
						ilgen.Emit(OpCodes.And);
					}
					else if (src == PrimitiveTypeWrapper.LONG)
					{
						ilgen.Emit(OpCodes.Conv_I4);
					}
					else if (src == PrimitiveTypeWrapper.FLOAT)
					{
						ilgen.Emit(OpCodes.Call, ByteCodeHelperMethods.f2i);
					}
					else if (src == PrimitiveTypeWrapper.DOUBLE)
					{
						ilgen.Emit(OpCodes.Call, ByteCodeHelperMethods.d2i);
					}
				}
				else
				{
					BoxUtil.Box(ilgen, srcClass, dstClass, level);
				}
			}
			else if (src.IsGhost)
			{
				src.EmitConvSignatureTypeToStackType(ilgen);
				EmitConvert(ilgen, ikvm.@internal.ClassLiteral<java.lang.Object>.Value, dstClass, level);
			}
			else if (srcClass == ikvm.@internal.ClassLiteral<sun.invoke.empty.Empty>.Value)
			{
				ilgen.Emit(OpCodes.Pop);
				ilgen.Emit(OpCodes.Ldloc, ilgen.DeclareLocal(dst.TypeAsSignatureType));
			}
			else if (dst.IsPrimitive)
			{
				BoxUtil.Unbox(ilgen, srcClass, dstClass, level);
			}
			else if (dst.IsGhost)
			{
				dst.EmitConvStackTypeToSignatureType(ilgen, null);
			}
			else if (dst.IsNonPrimitiveValueType)
			{
				dst.EmitUnbox(ilgen);
			}
			else
			{
				dst.EmitCheckcast(ilgen);
			}
		}
	}

	internal static void Dump(MethodHandle mh)
	{
		Console.WriteLine("----");
		Dump((Delegate)mh.vmtarget, 0);
	}

	private static void WriteNest(int nest)
	{
		for (int i = 0; i < nest; i++)
		{
			Console.Write("  ");
		}
	}

	private static void Dump(Delegate d, int nest)
	{
		if (nest > 0)
		{
			WriteNest(nest - 1);
			Console.Write("->");
		}
		Console.Write(d.Method.Name + "(");
		string sep = "";
		foreach (ParameterInfo pi in d.Method.GetParameters())
		{
			Console.WriteLine(sep);
			WriteNest(nest);
			Console.Write("  {0}", TypeToString(pi.ParameterType));
			sep = ",";
		}
		Console.WriteLine(")");
		WriteNest(nest);
		Console.WriteLine("  : {0}", TypeToString(d.Method.ReturnType));
		if (d.Target is Delegate)
		{
			Dump((Delegate)d.Target, nest == 0 ? 1 : nest);
		}
		else if (d.Target != null)
		{
			FieldInfo field = d.Target.GetType().GetField("value");
			if (field != null && field.GetValue(d.Target) is Delegate)
			{
				WriteNest(nest + 1);
				Console.WriteLine("Collector:");
				Dump((Delegate)field.GetValue(d.Target), nest + 2);
			}
			field = d.Target.GetType().GetField("target");
			if (field != null && field.GetValue(d.Target) != null)
			{
				Dump((Delegate)field.GetValue(d.Target), nest == 0 ? 1 : nest);
			}
		}
	}

	private static string TypeToString(Type type)
	{
		if (type.IsGenericType
			&& type.Namespace == "IKVM.Runtime"
			&& (type.Name.StartsWith("MH`") || type.Name.StartsWith("MHV`")))
		{
			return type.Name.Substring(0, type.Name.IndexOf('`')) + "<" + TypesToString(type.GetGenericArguments()) + ">";
		}
		else if (type.DeclaringType == typeof(DynamicMethodBuilder))
		{
			return "C<" + TypesToString(type.GetGenericArguments()) + ">";
		}
		else if (ReflectUtil.IsVector(type))
		{
			return TypeToString(type.GetElementType()) + "[]";
		}
		else if (type == typeof(object))
		{
			return "object";
		}
		else if (type == typeof(string))
		{
			return "string";
		}
		else if (type.IsPrimitive)
		{
			return type.Name.ToLowerInvariant();
		}
		else
		{
			return type.ToString();
		}
	}

	private static string TypesToString(Type[] types)
	{
		System.Text.StringBuilder sb = new System.Text.StringBuilder();
		foreach (Type type in types)
		{
			if (sb.Length != 0)
			{
				sb.Append(", ");
			}
			sb.Append(TypeToString(type));
		}
		return sb.ToString();
	}
#endif
}

static class Java_java_lang_invoke_AdapterMethodHandle
{
	public static MethodHandle makePairwiseConvert(MethodType newType, MethodHandle target, int level)
	{
#if FIRST_PASS
		return null;
#else
		MethodType oldType = target.type();
		MethodHandleUtil.DynamicMethodBuilder dm = new MethodHandleUtil.DynamicMethodBuilder("AdapterMethodHandle.pairwiseConvert", newType, target);
		for (int i = 0, count = newType.parameterCount(); i < count; i++)
		{
			dm.Ldarg(i);
			dm.Convert(newType.parameterType(i), oldType.parameterType(i), level);
		}
		dm.CallTarget();
		dm.Convert(oldType.returnType(), newType.returnType(), level);
		dm.Ret();
		return dm.CreateAdapter();
#endif
	}

	public static MethodHandle makeRetype(MethodType newType, MethodHandle target, bool raw)
	{
#if FIRST_PASS
		return null;
#else
		MethodType oldType = target.type();
		if (oldType == newType)
		{
			return target;
		}
		if (!AdapterMethodHandle.canRetype(newType, oldType, raw))
		{
			return null;
		}
		// TODO does raw translate into a level?
		return makePairwiseConvert(newType, target, 0);
#endif
	}

	public static MethodHandle makeSpreadArguments(MethodType newType, MethodHandle target, java.lang.Class spreadArgType, int spreadArgPos, int spreadArgCount)
	{
#if FIRST_PASS
		return null;
#else
		TypeWrapper twComponent = TypeWrapper.FromClass(spreadArgType).ElementTypeWrapper;
		MethodHandleUtil.DynamicMethodBuilder dm = new MethodHandleUtil.DynamicMethodBuilder("AdapterMethodHandle.spreadArguments", newType, target);
		for (int i = 0, count = newType.parameterCount(); i < count; i++)
		{
			if (i == spreadArgPos)
			{
				for (int j = 0; j < spreadArgCount; j++)
				{
					dm.Ldarg(i);
					dm.LoadArrayElement(j, twComponent);
					dm.Convert(twComponent.ClassObject, target.type().parameterType(i + j), 0);
				}
			}
			else
			{
				dm.Ldarg(i);
			}
		}
		dm.CallTarget();
		dm.Ret();
		return dm.CreateAdapter();
#endif
	}

	public static MethodHandle makeCollectArguments(MethodHandle target, MethodHandle collector, int collectArgPos, bool retainOriginalArgs)
	{
#if FIRST_PASS
		return null;
#else
		MethodType targetType = target.type();
		MethodType collectorType = collector.type();
		bool isfilter = collectorType.returnType() == java.lang.Void.TYPE;
		MethodType newType = targetType.dropParameterTypes(collectArgPos, collectArgPos + (isfilter ? 0 : 1));
		if (!retainOriginalArgs)
		{
			newType = newType.insertParameterTypes(collectArgPos, collectorType.parameterList());
		}
		MethodHandleUtil.DynamicMethodBuilder dm = new MethodHandleUtil.DynamicMethodBuilder("AdapterMethodHandle.collectArguments", newType, target, collector.vmtarget);
		for (int i = 0, count = newType.parameterCount(); i < count || i == collectArgPos; i++)
		{
			if (i == collectArgPos)
			{
				dm.LoadValue();
				for (int j = 0; j < collectorType.parameterCount(); j++)
				{
					dm.Ldarg(i + j);
				}
				dm.CallValue();

				collectArgPos = -1;
				i--;
				if (!retainOriginalArgs)
				{
					i += collectorType.parameterCount();
				}
			}
			else
			{
				dm.Ldarg(i);
			}
		}
		dm.CallTarget();
		dm.Ret();
		return dm.CreateAdapter();
#endif
	}
}

static class Java_java_lang_invoke_MethodHandleImpl
{
	public static MethodHandle permuteArguments(MethodHandle target, MethodType newType, MethodType oldType, int[] permutationOrNull)
	{
#if FIRST_PASS
		return null;
#else
		// LAME why does OpenJDK name the parameter permutationOrNull while it is not allowed to be null?
		if (permutationOrNull.Length != oldType.parameterCount())
		{
			throw new java.lang.IllegalArgumentException("wrong number of arguments in permutation");
		}
		MethodHandleUtil.DynamicMethodBuilder dm = new MethodHandleUtil.DynamicMethodBuilder("MethodHandleImpl.permuteArguments", newType, target);
		for (int i = 0, argCount = newType.parameterCount(); i < permutationOrNull.Length; i++)
		{
			// make sure to only read each array element once, to avoid having to make a defensive copy of the array
			int perm = permutationOrNull[i];
			if (perm < 0 || perm >= argCount)
			{
				throw new java.lang.IllegalArgumentException("permutation argument out of range");
			}
			dm.Ldarg(perm);
			dm.Convert(oldType.parameterType(i), newType.parameterType(perm), 0);
		}
		dm.CallTarget();
		dm.Convert(oldType.returnType(), newType.returnType(), 0);
		dm.Ret();
		return dm.CreateAdapter();
#endif
	}
}

static class Java_java_lang_invoke_MethodHandleNatives
{
	public static void init(MemberName self, object r)
	{
#if !FIRST_PASS
		if (r is java.lang.reflect.Method || r is java.lang.reflect.Constructor)
		{
			MethodWrapper mw = MethodWrapper.FromMethodOrConstructor(r);
			int index = Array.IndexOf(mw.DeclaringType.GetMethods(), mw);
			if (index != -1)
			{
				// TODO self.setVMIndex(index);
				typeof(MemberName).GetField("vmindex", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(self, index);
				typeof(MemberName).GetField("vmtarget", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(self, mw.DeclaringType);
				int flags = (int)mw.Modifiers;
				if (r is java.lang.reflect.Method)
				{
					flags |= MemberName.IS_METHOD;
				}
				else
				{
					flags |= MemberName.IS_CONSTRUCTOR;
				}
				typeof(MemberName).GetField("flags", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(self, flags);
			}
		}
		else if (r is java.lang.reflect.Field)
		{
			FieldWrapper fw = FieldWrapper.FromField((java.lang.reflect.Field)r);
			int index = Array.IndexOf(fw.DeclaringType.GetFields(), fw);
			if (index != -1)
			{
				// TODO self.setVMIndex(index);
				typeof(MemberName).GetField("vmindex", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(self, index);
				typeof(MemberName).GetField("flags", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(self, (int)fw.Modifiers | MemberName.IS_FIELD);
			}
		}
		else
		{
			throw new InvalidOperationException();
		}
#endif
	}

	public static void expand(MemberName self)
	{
		throw new NotImplementedException();
	}

	public static void resolve(MemberName self, jlClass caller)
	{
#if !FIRST_PASS
		if (self.isMethod() || self.isConstructor())
		{
			TypeWrapper tw = TypeWrapper.FromClass(self.getDeclaringClass());
			if (tw == CoreClasses.java.lang.invoke.MethodHandle.Wrapper
				&& (self.getName() == "invoke" || self.getName() == "invokeExact"))
			{
				typeof(MemberName).GetField("vmindex", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(self, Int32.MaxValue);
				return;
			}
			MethodWrapper mw = tw.GetMethodWrapper(self.getName(), self.getSignature().Replace('/', '.'), true);
			if (mw != null)
			{
				tw = mw.DeclaringType;
				int index = Array.IndexOf(tw.GetMethods(), mw);
				if (index != -1)
				{
					// TODO self.setVMIndex(index);
					typeof(MemberName).GetField("vmindex", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(self, index);
					typeof(MemberName).GetField("vmtarget", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(self, tw);
					int flags = (int)mw.Modifiers;
					if (self.isMethod())
					{
						flags |= MemberName.IS_METHOD;
					}
					else
					{
						flags |= MemberName.IS_CONSTRUCTOR;
					}
					typeof(MemberName).GetField("flags", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(self, flags);
				}
			}
		}
		else if (self.isField())
		{
			TypeWrapper tw = TypeWrapper.FromClass(self.getDeclaringClass());
			// TODO should we look in base classes?
			FieldWrapper fw = tw.GetFieldWrapper(self.getName(), self.getSignature().Replace('/', '.'));
			if (fw != null)
			{
				int index = Array.IndexOf(fw.DeclaringType.GetFields(), fw);
				if (index != -1)
				{
					// TODO self.setVMIndex(index);
					typeof(MemberName).GetField("vmindex", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(self, index);
					typeof(MemberName).GetField("flags", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(self, (int)fw.Modifiers | MemberName.IS_FIELD);
				}
			}
		}
		else
		{
			throw new InvalidOperationException();
		}
#endif
	}

	public static int getMembers(jlClass defc, string matchName, string matchSig, int matchFlags, jlClass caller, int skip, object[] results)
	{
		return 1;
	}
}
