using System.Collections.Generic;
using System.Linq;
using dnlib.DotNet;
using dnlib.DotNet.Emit;
using Obfuscator.Helper;

namespace Obfuscator.Obfuscator.ImportProtect;

internal class ImportProtect
{
	public static FieldDefUser CreateField(FieldSig sig)
	{
		return new FieldDefUser(Methods.GenerateString(), sig, FieldAttributes.Public | FieldAttributes.Static);
	}

	public static void Execute(ModuleDef module)
	{
		Dictionary<IMethod, MethodDef> dictionary = new Dictionary<IMethod, MethodDef>();
		Dictionary<IMethod, TypeDef> dictionary2 = new Dictionary<IMethod, TypeDef>();
		FieldDefUser fieldDefUser = CreateField(new FieldSig(module.ImportAsTypeSig(typeof(object[]))));
		MethodDef methodDef = module.GlobalType.FindOrCreateStaticConstructor();
		TypeDef[] array = module.GetTypes().ToArray();
		foreach (TypeDef typeDef in array)
		{
			if (typeDef.IsDelegate || typeDef.IsGlobalModuleType || typeDef.Namespace == "Costura")
			{
				continue;
			}
			MethodDef[] array2 = typeDef.Methods.ToArray();
			foreach (MethodDef methodDef2 in array2)
			{
				if (!methodDef2.HasBody || !methodDef2.Body.HasInstructions || methodDef2.IsConstructor)
				{
					continue;
				}
				IList<Instruction> instructions = methodDef2.Body.Instructions;
				for (int k = 0; k < instructions.Count; k++)
				{
					if ((instructions[k].OpCode != OpCodes.Call && instructions[k].OpCode == OpCodes.Callvirt) || !(instructions[k].Operand is IMethod { IsMethodDef: not false } method))
					{
						continue;
					}
					MethodDef methodDef3 = method.ResolveMethodDef();
					if (methodDef3 == null || methodDef3.HasThis)
					{
						continue;
					}
					if (dictionary.ContainsKey(method))
					{
						instructions[k].OpCode = OpCodes.Call;
						instructions[k].Operand = dictionary[method];
						continue;
					}
					MethodSig methodSig = CreateProxySignature(module, methodDef3);
					TypeDef typeDef2 = CreateDelegateType(module, methodSig);
					module.Types.Add(typeDef2);
					MethodImplAttributes implFlags = MethodImplAttributes.IL;
					MethodAttributes flags = MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig;
					MethodDefUser methodDefUser = new MethodDefUser(Methods.GenerateString(), methodSig, implFlags, flags);
					methodDefUser.Body = new CilBody();
					methodDefUser.Body.Instructions.Add(OpCodes.Ldsfld.ToInstruction(fieldDefUser));
					methodDefUser.Body.Instructions.Add(OpCodes.Ldc_I4.ToInstruction(dictionary2.Count));
					methodDefUser.Body.Instructions.Add(OpCodes.Ldelem_Ref.ToInstruction());
					foreach (Parameter parameter in methodDefUser.Parameters)
					{
						parameter.Name = Methods.GenerateString();
						methodDefUser.Body.Instructions.Add(OpCodes.Ldarg.ToInstruction(parameter));
					}
					methodDefUser.Body.Instructions.Add(OpCodes.Call.ToInstruction(typeDef2.Methods[1]));
					methodDefUser.Body.Instructions.Add(OpCodes.Ret.ToInstruction());
					typeDef2.Methods.Add(methodDefUser);
					instructions[k].OpCode = OpCodes.Call;
					instructions[k].Operand = methodDefUser;
					if (method.IsMethodDef)
					{
						dictionary2.Add(methodDef3, typeDef2);
					}
					else if (method.IsMemberRef)
					{
						dictionary2.Add(method as MemberRef, typeDef2);
					}
					dictionary.Add(method, methodDefUser);
				}
			}
		}
		module.GlobalType.Fields.Add(fieldDefUser);
		List<Instruction> list = new List<Instruction>();
		List<Instruction> list2 = methodDef.Body.Instructions.ToList();
		methodDef.Body.Instructions.Clear();
		list.Add(OpCodes.Ldc_I4.ToInstruction(dictionary2.Count));
		list.Add(OpCodes.Newarr.ToInstruction(module.CorLibTypes.Object));
		list.Add(OpCodes.Dup.ToInstruction());
		int num = 0;
		foreach (KeyValuePair<IMethod, TypeDef> item in dictionary2)
		{
			list.Add(OpCodes.Ldc_I4.ToInstruction(num));
			list.Add(OpCodes.Ldnull.ToInstruction());
			list.Add(OpCodes.Ldftn.ToInstruction(item.Key));
			list.Add(OpCodes.Newobj.ToInstruction(item.Value.Methods[0]));
			list.Add(OpCodes.Stelem_Ref.ToInstruction());
			list.Add(OpCodes.Dup.ToInstruction());
			num++;
		}
		list.Add(OpCodes.Pop.ToInstruction());
		list.Add(OpCodes.Stsfld.ToInstruction(fieldDefUser));
		foreach (Instruction item2 in list)
		{
			methodDef.Body.Instructions.Add(item2);
		}
		foreach (Instruction item3 in list2)
		{
			methodDef.Body.Instructions.Add(item3);
		}
	}

	public static TypeDef CreateDelegateType(ModuleDef module, MethodSig sig)
	{
		TypeDefUser obj = new TypeDefUser(Methods.GenerateString(), module.CorLibTypes.GetTypeRef("System", "MulticastDelegate"))
		{
			Attributes = (TypeAttributes.Public | TypeAttributes.Sealed)
		};
		MethodDefUser item = new MethodDefUser(".ctor", MethodSig.CreateInstance(module.CorLibTypes.Void, module.CorLibTypes.Object, module.CorLibTypes.IntPtr))
		{
			Attributes = (MethodAttributes.Assembly | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName),
			ImplAttributes = MethodImplAttributes.CodeTypeMask
		};
		obj.Methods.Add(item);
		MethodDefUser item2 = new MethodDefUser("Invoke", sig.Clone())
		{
			MethodSig = 
			{
				HasThis = true
			},
			Attributes = (MethodAttributes.Assembly | MethodAttributes.Virtual | MethodAttributes.HideBySig | MethodAttributes.VtableLayoutMask),
			ImplAttributes = MethodImplAttributes.CodeTypeMask
		};
		obj.Methods.Add(item2);
		return obj;
	}

	public static MethodSig CreateProxySignature(ModuleDef module, IMethod method)
	{
		IEnumerable<TypeSig> enumerable = method.MethodSig.Params.Select((TypeSig type) => (type.IsClassSig && method.MethodSig.HasThis) ? module.CorLibTypes.Object : type);
		if (method.MethodSig.HasThis && !method.MethodSig.ExplicitThis)
		{
			TypeDef typeDef = method.DeclaringType.ResolveTypeDefThrow();
			enumerable = (typeDef.IsValueType ? new TypeSig[1] { typeDef.ToTypeSig() }.Concat(enumerable) : new CorLibTypeSig[1] { module.CorLibTypes.Object }.Concat(enumerable));
		}
		TypeSig typeSig = method.MethodSig.RetType;
		if (typeSig.IsClassSig)
		{
			typeSig = module.CorLibTypes.Object;
		}
		return MethodSig.CreateStatic(typeSig, enumerable.ToArray());
	}
}
