using CPF.Mac.Foundation;
using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.InteropServices;

namespace CPF.Mac.ObjCRuntime
{
	internal class NativeMethodBuilder : NativeImplementationBuilder
	{
		private static MethodInfo creatensstring = typeof(NSString).GetMethod("op_Explicit", new Type[1]
		{
			typeof(string)
		});

		private static MethodInfo convertstruct = typeof(Marshal).GetMethod("StructureToPtr", new Type[3]
		{
			typeof(object),
			typeof(IntPtr),
			typeof(bool)
		});

		private static MethodInfo buildarray = typeof(NSArray).GetMethod("FromNSObjects", new Type[1]
		{
			typeof(NSObject[])
		});

		private static MethodInfo buildsarray = typeof(NSArray).GetMethod("FromStrings", new Type[1]
		{
			typeof(string[])
		});

		private MethodInfo minfo;

		private Type type;

		private Type rettype;

		private bool isstret;

		internal NativeMethodBuilder(MethodInfo minfo)
			: this(minfo, minfo.DeclaringType, (ExportAttribute)Attribute.GetCustomAttribute(minfo.GetBaseDefinition(), typeof(ExportAttribute)))
		{
		}

		internal NativeMethodBuilder(MethodInfo minfo, Type type, ExportAttribute ea)
		{
			if (ea == null)
			{
				throw new ArgumentException("MethodInfo does not have a export attribute");
			}
			if (minfo.DeclaringType.IsGenericType)
			{
				throw new ArgumentException("MethodInfo cannot be in a generic type");
			}
			base.Parameters = minfo.GetParameters();
			rettype = ConvertReturnType(minfo.ReturnType);
			base.Selector = new Selector(ea.Selector ?? minfo.Name, alloc: true).Handle;
			base.Signature = $"{TypeConverter.ToNative(minfo.ReturnType)}@:";
			ConvertParameters(base.Parameters, minfo.IsStatic, isstret);
			base.DelegateType = CreateDelegateType(rettype, base.ParameterTypes);
			this.minfo = minfo;
			this.type = type;
		}

		internal override Delegate CreateDelegate()
		{
			DynamicMethod dynamicMethod = new DynamicMethod($"[{minfo.DeclaringType}:{minfo}]", rettype, base.ParameterTypes, NativeImplementationBuilder.module, skipVisibility: true);
			ILGenerator iLGenerator = dynamicMethod.GetILGenerator();
			DeclareLocals(iLGenerator);
			ConvertArguments(iLGenerator, 0);
			if (!minfo.IsStatic)
			{
				iLGenerator.Emit(OpCodes.Ldarg, isstret ? 1 : 0);
				iLGenerator.Emit(OpCodes.Castclass, type);
			}
			LoadArguments(iLGenerator, 0);
			if (minfo.IsVirtual)
			{
				iLGenerator.Emit(OpCodes.Callvirt, minfo);
			}
			else
			{
				iLGenerator.Emit(OpCodes.Call, minfo);
			}
			UpdateByRefArguments(iLGenerator, 0);
			if (minfo.ReturnType == typeof(string))
			{
				iLGenerator.Emit(OpCodes.Call, creatensstring);
			}
			else if (minfo.ReturnType.IsArray && IsWrappedType(minfo.ReturnType.GetElementType()))
			{
				if (minfo.ReturnType.GetElementType() == typeof(string))
				{
					iLGenerator.Emit(OpCodes.Call, buildsarray);
				}
				else
				{
					iLGenerator.Emit(OpCodes.Call, buildarray);
				}
			}
			else if (typeof(INativeObject).IsAssignableFrom(minfo.ReturnType) && !IsWrappedType(minfo.ReturnType))
			{
				iLGenerator.Emit(OpCodes.Call, minfo.ReturnType.GetProperty("Handle").GetGetMethod());
			}
			else if (isstret)
			{
				iLGenerator.Emit(OpCodes.Box, minfo.ReturnType);
				iLGenerator.Emit(OpCodes.Ldarg, 0);
				iLGenerator.Emit(OpCodes.Ldc_I4, 0);
				iLGenerator.Emit(OpCodes.Call, convertstruct);
			}
			iLGenerator.Emit(OpCodes.Ret);
			return dynamicMethod.CreateDelegate(base.DelegateType);
		}

		private Type ConvertReturnType(Type type)
		{
			if (type.IsValueType && !type.IsEnum && type.Assembly != typeof(object).Assembly && Marshal.SizeOf(type) > IntPtr.Size * 2)
			{
				isstret = true;
				return typeof(void);
			}
			if (type == typeof(string))
			{
				return typeof(NSString);
			}
			if (type.IsArray && IsWrappedType(type.GetElementType()))
			{
				return typeof(NSArray);
			}
			if (typeof(INativeObject).IsAssignableFrom(type) && !IsWrappedType(type))
			{
				return typeof(IntPtr);
			}
			return type;
		}
	}
}
